123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- import json, urllib
- from django.conf import settings
- from django.core import exceptions
- from django.utils import crypto, importlib
- from mongoengine import queryset
- from mongoengine.django import auth
- import tweepy
- from django_browserid import auth as browserid_auth, base as browserid_base
- from . import models
- LAZYUSER_USERNAME_TEMPLATE = 'guest-%s'
- USER_CLASS = 'mongo_auth.models.User'
- def get_class(path):
- i = path.rfind('.')
- module, attr = path[:i], path[i+1:]
- try:
- mod = importlib.import_module(module)
- except ImportError, e:
- raise exceptions.ImproperlyConfigured('Error importing user class module %s: "%s"' % (path, e))
- except ValueError, e:
- raise exceptions.ImproperlyConfigured('Error importing user class module. Is USER_CLASS a correctly defined class path?')
- try:
- cls = getattr(mod, attr)
- except AttributeError:
- raise exceptions.ImproperlyConfigured('Module "%s" does not define user class "%s"' % (module, attr))
- return cls
- User = get_class(getattr(settings, 'USER_CLASS', USER_CLASS))
- class MongoEngineBackend(auth.MongoEngineBackend):
- # TODO: Implement object permission support
- supports_object_permissions = False
- # TODO: Implement anonymous user backend
- supports_anonymous_user = False
- # TODO: Implement inactive user backend
- supports_inactive_user = False
- def authenticate(self, username, password):
- user = self.user_class.objects(username__iexact=username).first()
- if user:
- if password and user.check_password(password):
- return user
- return None
- def get_user(self, user_id):
- try:
- return self.user_class.objects.with_id(user_id)
- except self.user_class.DoesNotExist:
- return None
- @property
- def user_class(self):
- return User
- class FacebookBackend(MongoEngineBackend):
- """
- Facebook authentication.
-
- Facebook uses strings 'male' and 'female' for representing user gender.
- """
- # TODO: List all profile data fields we (can) get
- def authenticate(self, facebook_access_token, request):
- # Retrieve user's profile information
- # TODO: Handle error, what if request was denied?
- facebook_profile_data = json.load(urllib.urlopen('https://graph.facebook.com/me?%s' % urllib.urlencode({'access_token': facebook_access_token})))
- try:
- user = self.user_class.objects.get(facebook_profile_data__id=facebook_profile_data.get('id'))
- except self.user_class.DoesNotExist:
- # TODO: Based on user preference, we might create a new user here, not just link with existing, if existing user is lazy user
- # We reload to make sure user object is recent
- request.user.reload()
- user = request.user
- # TODO: Is it OK to override Facebook link if it already exist with some other Facebook user?
- user.facebook_access_token = facebook_access_token
- user.facebook_profile_data = facebook_profile_data
- user.authenticate_facebook(request)
- user.save()
- return user
- class TwitterBackend(MongoEngineBackend):
- """
- Twitter authentication.
- Twitter profile data fields are:
- name: user's full name
- screen_name: user's username
- profile_image_url
- profile_background_image_url
- id: id of Twitter user
- id_str: string id of Twitter user
- created_at: full date of user's registration on Twitter
- location: user's location
- time_zone
- lang: user's preferred language
- url: URL of user's website
- description: user's description of themselves
- profile_sidebar_fill_color
- profile_text_color
- profile_background_color
- profile_link_color
- profile_sidebar_border_color
- friends_count
- followers_count
- statuses_count
- favourites_count
- listed_count
- notifications: boolean value
- geo_enabled: boolean value
- following: boolean value
- follow_request_sent: boolean value
- profile_use_background_image: boolean value
- verified: boolean value; tells whether user's email is verified
- protected: boolean value
- show_all_inline_media: boolean value
- is_translator: boolean value
- profile_background_tile: boolean value
- contributors_enabled: boolean value
- utc_offset: integer
- """
- def authenticate(self, twitter_access_token, request):
- TWITTER_CONSUMER_KEY = getattr(settings, 'TWITTER_CONSUMER_KEY', None)
- TWITTER_CONSUMER_SECRET = getattr(settings, 'TWITTER_CONSUMER_SECRET', None)
- if not TWITTER_CONSUMER_KEY or not TWITTER_CONSUMER_SECRET:
- return None
- twitter_auth = tweepy.OAuthHandler(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET)
- twitter_auth.set_access_token(twitter_access_token.key, twitter_access_token.secret)
- twitter_api = tweepy.API(twitter_auth)
- twitter_profile_data = twitter_api.me()
- if 'id' not in twitter_profile_data:
- return None
- try:
- user = self.user_class.objects.get(twitter_profile_data__id=twitter_profile_data['id'])
- except self.user_class.DoesNotExist:
- # TODO: Based on user preference, we might create a new user here, not just link with existing, if existing user is lazy user
- # We reload to make sure user object is recent
- request.user.reload()
- user = request.user
- # TODO: Is it OK to override Twitter link if it already exist with some other Twitter user?
- user.twitter_access_token = models.TwitterAccessToken(key=twitter_access_token.key, secret=twitter_access_token.secret)
- user.twitter_profile_data = twitter_profile_data
- user.authenticate_twitter(request)
- user.save()
- return user
- class GoogleBackend(MongoEngineBackend):
- """
- Google authentication.
- Google profile data fields are:
- family_name: last name
- given_name: first name
- name: full name
- link: URL of Google user profile page
- picture: URL of profile picture
- locale: the language Google user is using
- gender: the gender of Google user (other|female|male)
- timezone: the default timezone of Google user
- email: Google email of user
- id: id of Google user; should be a string
- verified_email: True, if email is verified by Google API
- """
- def authenticate(self, google_access_token, request):
- # Retrieve user's profile information
- # TODO: Handle error, what if request was denied?
- google_profile_data = json.load(urllib.urlopen('https://www.googleapis.com/oauth2/v1/userinfo?access_token=%s' % google_access_token))
- if 'id' not in google_profile_data:
- return None
- try:
- user = self.user_class.objects.get(google_profile_data__id=google_profile_data['id'])
- except self.user_class.DoesNotExist:
- # TODO: Based on user preference, we might create a new user here, not just link with existing, if existing user is lazy user
- # We reload to make sure user object is recent
- request.user.reload()
- user = request.user
- # TODO: Is it OK to override Google link if it already exist with some other Google user?
- user.google_access_token = google_access_token
- user.google_profile_data = google_profile_data
- user.authenticate_google(request)
- user.save()
- return user
- class FoursquareBackend(MongoEngineBackend):
- """
- Foursquare authentication.
- Foursquare profile data fields are:
- id: a unique identifier for this user.
- firstName: user's first name
- lastName: user's last name
- homeCity: user's home city
- photo: URL of a profile picture for this user
- gender: user's gender: male, female, or none
- relationship: the relationship of the acting user (me) to this user (them) (optional)
- type: one of page, celebrity, or user
- contact: an object containing none, some, or all of twitter, facebook, email, and phone
- pings: pings from this user (optional)
- badges: contains the count of badges for this user, may eventually contain some selected badges
- checkins: contains the count of checkins by this user, may contain the most recent checkin as an array
- mayorships: contains the count of mayorships for this user and an items array that for now is empty
- tips: contains the count of tips from this user, may contain an array of selected tips as items
- todos: contains the count of todos this user has, may contain an array of selected todos as items
- photos: contains the count of photos this user has, may contain an array of selected photos as items
- friends: contains count of friends for this user and groups of users who are friends
- followers: contains count of followers for this user, if they are a page or celebrity
- requests: contains count of pending friend requests for this user
- pageInfo: contains a detailed page, if they are a page
- """
- def authenticate(self, foursquare_access_token, request):
- # Retrieve user's profile information
- # TODO: Handle error, what if request was denied?
- foursquare_profile_data = json.load(urllib.urlopen('https://api.foursquare.com/v2/users/self?%s' % urllib.urlencode({'oauth_token': foursquare_access_token})))['response']['user']
- if 'id' not in foursquare_profile_data:
- return None
- try:
- user = self.user_class.objects.get(foursquare_profile_data__id=foursquare_profile_data['id'])
- except self.user_class.DoesNotExist:
- # TODO: Based on user preference, we might create a new user here, not just link with existing, if existing user is lazy user
- # We reload to make sure user object is recent
- request.user.reload()
- user = request.user
- # TODO: Is it OK to override Foursquare link if it already exist with some other Foursquare user?
- user.foursquare_access_token = foursquare_access_token
- user.foursquare_profile_data = foursquare_profile_data
- user.authenticate_foursquare(request)
- user.save()
- return user
- class BrowserIDBackend(MongoEngineBackend, browserid_auth.BrowserIDBackend):
- """
- Persona authentication.
- Persona profile data fields are:
- email: email user uses for Persona
- """
-
- def authenticate(self, browserid_assertion=None, browserid_audience=None, request=None):
- browserid_profile_data = browserid_base.verify(browserid_assertion, browserid_audience)
- if not browserid_profile_data or 'email' not in browserid_profile_data:
- return None
- try:
- user = self.user_class.objects.get(browserid_profile_data__email=browserid_profile_data['email'])
- # TODO: What is we get more than one user?
- except self.user_class.DoesNotExist:
- # TODO: Based on user preference, we might create a new user here, not just link with existing, if existing user is lazy user
- # We reload to make sure user object is recent
- request.user.reload()
- user = request.user
- # TODO: Is it OK to override BrowserID link if it already exist with some other BrowserID user?
-
- user.browserid_profile_data = browserid_profile_data
- user.authenticate_browserid(request)
- user.save()
- return user
- class LazyUserBackend(MongoEngineBackend):
- def authenticate(self, request):
- while True:
- try:
- username = LAZYUSER_USERNAME_TEMPLATE % crypto.get_random_string(6)
- user = self.user_class.create_user(username=username, lazyuser=True)
- user.authenticate_lazyuser(request)
- user.save()
- break
- except queryset.OperationError, e:
- msg = str(e)
- if 'E11000' in msg and 'duplicate key error' in msg and 'username' in msg:
- continue
- raise
- return user
|