models.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import hashlib, urllib
  2. from django.conf import settings
  3. from django.contrib.auth import hashers, models as auth_models
  4. from django.contrib.staticfiles.storage import staticfiles_storage
  5. from django.core import mail
  6. from django.test import client
  7. from django.utils import timezone
  8. from django.utils.translation import ugettext_lazy as _
  9. import mongoengine
  10. from mongoengine.django import auth
  11. from . import utils
  12. USERNAME_REGEX = r'[\w.@+-]+'
  13. CONFIRMATION_TOKEN_VALIDITY = 5 # days
  14. DEFAULT_USER_IMAGE = 'mongo_auth/images/unknown.png'
  15. # Used to reconstruct absolute/full URLs where request is not available
  16. DEFAULT_REQUEST = {
  17. 'SERVER_NAME': '127.0.0.1',
  18. 'SERVER_PORT': '8000',
  19. }
  20. class EmailConfirmationToken(mongoengine.EmbeddedDocument):
  21. value = mongoengine.StringField(max_length=20, required=True)
  22. created_time = mongoengine.DateTimeField(default=lambda: timezone.now(), required=True)
  23. def check_token(self, confirmation_token):
  24. if confirmation_token != self.value:
  25. return False
  26. elif (timezone.now() - self.created_time).days > CONFIRMATION_TOKEN_VALIDITY:
  27. return False
  28. else:
  29. return True
  30. class TwitterAccessToken(mongoengine.EmbeddedDocument):
  31. key = mongoengine.StringField(max_length=150)
  32. secret = mongoengine.StringField(max_length=150)
  33. class User(auth.User):
  34. username = mongoengine.StringField(
  35. max_length=30,
  36. min_length=4,
  37. regex=r'^' + USERNAME_REGEX + r'$',
  38. required=True,
  39. verbose_name=_("username"),
  40. help_text=_("Minimal of 4 characters and maximum of 30. Letters, digits and @/./+/-/_ only."),
  41. )
  42. lazyuser_username = mongoengine.BooleanField(default=True)
  43. facebook_access_token = mongoengine.StringField(max_length=255)
  44. facebook_profile_data = mongoengine.DictField()
  45. twitter_access_token = mongoengine.EmbeddedDocumentField(TwitterAccessToken)
  46. twitter_profile_data = mongoengine.DictField()
  47. google_access_token = mongoengine.StringField(max_length=150)
  48. google_profile_data = mongoengine.DictField()
  49. foursquare_access_token = mongoengine.StringField(max_length=150)
  50. foursquare_profile_data = mongoengine.DictField()
  51. browserid_profile_data = mongoengine.DictField()
  52. email_confirmed = mongoengine.BooleanField(default=False)
  53. email_confirmation_token = mongoengine.EmbeddedDocumentField(EmailConfirmationToken)
  54. @classmethod
  55. def get_initial_fields(cls, request):
  56. return {}
  57. def is_anonymous(self):
  58. return not self.is_authenticated()
  59. def is_authenticated(self):
  60. # TODO: Check if *_data fields are really false if not linked with third-party authentication
  61. return self.has_usable_password() or \
  62. self.facebook_profile_data or \
  63. self.twitter_profile_data or \
  64. self.google_profile_data or \
  65. self.foursquare_profile_data or \
  66. self.browserid_profile_data
  67. def check_password(self, raw_password):
  68. def setter(raw_password):
  69. self.set_password(raw_password)
  70. return hashers.check_password(raw_password, self.password, setter)
  71. def set_unusable_password(self):
  72. self.password = hashers.make_password(None)
  73. self.save()
  74. return self
  75. def has_usable_password(self):
  76. return hashers.is_password_usable(self.password)
  77. def email_user(self, subject, message, from_email=None, allow_unconfirmed=False):
  78. if not self.email:
  79. raise ValueError("Account e-mail address not set.")
  80. if not allow_unconfirmed and not self.email_confirmed:
  81. raise ValueError("Account e-mail address not confirmed.")
  82. mail.send_mail(subject, message, from_email, [self.email])
  83. def get_image_url(self):
  84. if self.twitter_profile_data and 'profile_image_url' in self.twitter_profile_data:
  85. return self.twitter_profile_data['profile_image_url']
  86. elif self.facebook_profile_data:
  87. # TODO: Do we really need this utils/graph_api_url?
  88. return '%s?type=square' % utils.graph_api_url('%s/picture' % self.username)
  89. elif self.foursquare_profile_data and 'photo' in self.foursquare_profile_data:
  90. return self.foursquare_profile_data['photo']
  91. elif self.google_profile_data and 'picture' in self.google_profile_data:
  92. return self.google_profile_data['picture']
  93. elif self.email:
  94. request = client.RequestFactory(**getattr(settings, 'DEFAULT_REQUEST', DEFAULT_REQUEST)).request()
  95. default_url = request.build_absolute_uri(staticfiles_storage.url(getattr(settings, 'DEFAULT_USER_IMAGE', DEFAULT_USER_IMAGE)))
  96. return 'https://secure.gravatar.com/avatar/%(email_hash)s?%(args)s' % {
  97. 'email_hash': hashlib.md5(self.email.lower()).hexdigest(),
  98. 'args': urllib.urlencode({
  99. 'default': default_url,
  100. 'size': 50,
  101. }),
  102. }
  103. else:
  104. return staticfiles_storage.url(getattr(settings, 'DEFAULT_USER_IMAGE', DEFAULT_USER_IMAGE))
  105. @classmethod
  106. def create_user(cls, username, email=None, password=None, lazyuser=False):
  107. now = timezone.now()
  108. if not username:
  109. raise ValueError("The given username must be set")
  110. email = auth_models.UserManager.normalize_email(email)
  111. user = cls(
  112. username=username,
  113. lazyuser_username=lazyuser,
  114. email=email or None,
  115. is_staff=False,
  116. is_active=True,
  117. is_superuser=False,
  118. last_login=now,
  119. date_joined=now,
  120. )
  121. user.set_password(password)
  122. user.save()
  123. return user
  124. def authenticate_facebook(self, request):
  125. if self.lazyuser_username and self.facebook_profile_data.get('username'):
  126. # TODO: Does Facebook have same restrictions on username content as we do?
  127. self.username = self.facebook_profile_data.get('username')
  128. self.lazyuser_username = False
  129. if self.first_name is None:
  130. self.first_name = self.facebook_profile_data.get('first_name') or None
  131. if self.last_name is None:
  132. self.last_name = self.facebook_profile_data.get('last_name') or None
  133. if self.email is None:
  134. # TODO: Do we know if all e-mail addresses given by Facebook are verified?
  135. # TODO: Does not Facebook support multiple e-mail addresses? Which one is given here?
  136. self.email = self.facebook_profile_data.get('email') or None
  137. def authenticate_twitter(self, request):
  138. if self.lazyuser_username and self.twitter_profile_data.get('screen_name'):
  139. # TODO: Does Twitter have same restrictions on username content as we do?
  140. self.username = self.twitter_profile_data.get('screen_name')
  141. self.lazyuser_username = False
  142. if self.first_name is None:
  143. self.first_name = self.twitter_profile_data.get('name') or None
  144. def authenticate_google(self, request):
  145. username_guess = self.google_profile_data.get('email', '').rsplit('@', 1)[0]
  146. if self.lazyuser_username and username_guess:
  147. # Best username guess we can get from Google OAuth
  148. self.username = username_guess
  149. self.lazyuser_username = False
  150. if self.first_name is None:
  151. self.first_name = self.google_profile_data.get('given_name') or None
  152. if self.last_name is None:
  153. self.last_name = self.google_profile_data.get('family_name') or None
  154. if self.email is None:
  155. self.email = self.google_profile_data.get('email') or None
  156. if self.google_profile_data.get('verified_email'):
  157. self.email_confirmed = True
  158. def authenticate_foursquare(self, request):
  159. username_guess = self.foursquare_profile_data.get('contact', {}).get('email', '').rsplit('@', 1)[0]
  160. if self.lazyuser_username and username_guess:
  161. # Best username guess we can get from Foursquare
  162. self.username = username_guess
  163. self.lazyuser_username = False
  164. if self.first_name is None:
  165. self.first_name = self.foursquare_profile_data.get('firstName') or None
  166. if self.last_name is None:
  167. self.last_name = self.foursquare_profile_data.get('lastName') or None
  168. if self.email is None:
  169. self.email = self.foursquare_profile_data.get('contact', {}).get('email') or None
  170. def authenticate_browserid(self, request):
  171. if self.lazyuser_username:
  172. # Best username guess we can get from BrowserID
  173. self.username = self.browserid_profile_data['email'].rsplit('@', 1)[0]
  174. self.lazyuser_username = False
  175. if self.email is None:
  176. self.email = self.browserid_profile_data['email'] or None
  177. # BrowserID takes care of email confirmation
  178. self.email_confirmed = True
  179. def authenticate_lazyuser(self, request):
  180. pass