pipeline.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import json
  2. from django.contrib.auth import get_user_model
  3. from django.db import IntegrityError
  4. from django.http import JsonResponse
  5. from django.shortcuts import render
  6. from django.urls import reverse
  7. from django.utils.translation import gettext as _
  8. from social_core.pipeline.partial import partial
  9. from misago.core.exceptions import SocialAuthFailed, SocialAuthBanned
  10. from misago.legal.models import Agreement
  11. from misago.users.bans import get_request_ip_ban, get_user_ban
  12. from misago.users.forms.register import SocialAuthRegisterForm
  13. from misago.users.models import Ban
  14. from misago.users.registration import (
  15. get_registration_result_json, save_user_agreements, send_welcome_email
  16. )
  17. from misago.users.setupnewuser import setup_new_user
  18. from misago.users.validators import (
  19. ValidationError, validate_new_registration, validate_email, validate_username
  20. )
  21. from .utils import get_social_auth_backend_name, perpare_username
  22. UserModel = get_user_model()
  23. def validate_ip_not_banned(strategy, details, backend, user=None, *args, **kwargs):
  24. """Pipeline step that interrupts pipeline if found user is non-staff and IP banned"""
  25. if not user or user.is_staff:
  26. return None
  27. ban = get_request_ip_ban(strategy.request)
  28. if ban:
  29. hydrated_ban = Ban(
  30. check_type=Ban.IP,
  31. user_message=ban['message'],
  32. expires_on=ban['expires_on'],
  33. )
  34. raise SocialAuthBanned(backend, hydrated_ban)
  35. def validate_user_not_banned(strategy, details, backend, user=None, *args, **kwargs):
  36. """Pipeline step that interrupts pipeline if found user is non-staff and banned"""
  37. if not user or user.is_staff:
  38. return None
  39. user_ban = get_user_ban(user, strategy.request.cache_versions)
  40. if user_ban:
  41. raise SocialAuthBanned(backend, user_ban)
  42. def associate_by_email(strategy, details, backend, user=None, *args, **kwargs):
  43. """If user with e-mail from provider exists in database and is active,
  44. this step authenticates them.
  45. """
  46. if user:
  47. return None
  48. email = details.get('email')
  49. if not email:
  50. return None
  51. try:
  52. user = UserModel.objects.get_by_email(email)
  53. except UserModel.DoesNotExist:
  54. return None
  55. backend_name = get_social_auth_backend_name(backend.name)
  56. if not user.is_active:
  57. raise SocialAuthFailed(
  58. backend,
  59. _(
  60. "The e-mail address associated with your %(backend)s account is "
  61. "not available for use on this site."
  62. ) % {'backend': backend_name}
  63. )
  64. if user.requires_activation_by_admin:
  65. raise SocialAuthFailed(
  66. backend,
  67. _(
  68. "Your account has to be activated by site administrator before you will be able to "
  69. "sign in with %(backend)s."
  70. ) % {'backend': backend_name}
  71. )
  72. return {'user': user, 'is_new': False}
  73. def get_username(strategy, details, backend, user=None, *args, **kwargs):
  74. """Resolve valid username for use in new account"""
  75. if user:
  76. return None
  77. settings = strategy.request.settings
  78. username = perpare_username(details.get('username', ''))
  79. full_name = perpare_username(details.get('full_name', ''))
  80. first_name = perpare_username(details.get('first_name', ''))
  81. last_name = perpare_username(details.get('last_name', ''))
  82. names_to_try = [
  83. username,
  84. first_name,
  85. ]
  86. if username:
  87. names_to_try.append(username)
  88. if first_name:
  89. names_to_try.append(first_name)
  90. if last_name:
  91. # if first name is taken, try first name + first char of last name
  92. names_to_try.append(first_name + last_name[0])
  93. if full_name:
  94. names_to_try.append(full_name)
  95. username_length_max = settings.username_length_max
  96. for name in names_to_try:
  97. if len(name) > username_length_max:
  98. names_to_try.append(name[:username_length_max])
  99. for name in filter(bool, names_to_try):
  100. try:
  101. validate_username(settings, name)
  102. return {'clean_username': name}
  103. except ValidationError:
  104. pass
  105. def create_user(strategy, details, backend, user=None, *args, **kwargs):
  106. """Aggressively attempt to register and sign in new user"""
  107. if user:
  108. return None
  109. request = strategy.request
  110. settings = request.settings
  111. email = details.get('email')
  112. username = kwargs.get('clean_username')
  113. if not email or not username:
  114. return None
  115. try:
  116. validate_email(email)
  117. validate_new_registration(request, {
  118. 'email': email,
  119. 'username': username,
  120. })
  121. except ValidationError:
  122. return None
  123. activation_kwargs = {}
  124. if settings.account_activation == 'admin':
  125. activation_kwargs = {'requires_activation': UserModel.ACTIVATION_ADMIN}
  126. new_user = UserModel.objects.create_user(
  127. username,
  128. email,
  129. joined_from_ip=request.user_ip,
  130. **activation_kwargs
  131. )
  132. setup_new_user(settings, new_user)
  133. send_welcome_email(request, new_user)
  134. return {'user': new_user, 'is_new': True}
  135. @partial
  136. def create_user_with_form(strategy, details, backend, user=None, *args, **kwargs):
  137. """Alternatively to create_user lets user confirm account creation before authenticating"""
  138. if user:
  139. return None
  140. request = strategy.request
  141. settings = request.settings
  142. backend_name = get_social_auth_backend_name(backend.name)
  143. if request.method == 'POST':
  144. try:
  145. request_data = json.loads(request.body)
  146. except (TypeError, ValueError):
  147. request_data = request.POST.copy()
  148. form = SocialAuthRegisterForm(
  149. request_data,
  150. request=request,
  151. agreements=Agreement.objects.get_agreements(),
  152. )
  153. if not form.is_valid():
  154. return JsonResponse(form.errors, status=400)
  155. email_verified = form.cleaned_data['email'] == details.get('email')
  156. activation_kwargs = {}
  157. if settings.account_activation == 'admin':
  158. activation_kwargs = {'requires_activation': UserModel.ACTIVATION_ADMIN}
  159. elif settings.account_activation == 'user' and not email_verified:
  160. activation_kwargs = {'requires_activation': UserModel.ACTIVATION_USER}
  161. try:
  162. new_user = UserModel.objects.create_user(
  163. form.cleaned_data['username'],
  164. form.cleaned_data['email'],
  165. joined_from_ip=request.user_ip,
  166. **activation_kwargs
  167. )
  168. setup_new_user(settings, new_user)
  169. except IntegrityError:
  170. return JsonResponse({'__all__': _("Please try resubmitting the form.")}, status=400)
  171. save_user_agreements(new_user, form)
  172. send_welcome_email(request, new_user)
  173. return {'user': new_user, 'is_new': True}
  174. request.frontend_context['SOCIAL_AUTH'] = {
  175. 'backend_name': backend_name,
  176. 'step': 'register',
  177. 'email': details.get('email'),
  178. 'username': kwargs.get('clean_username'),
  179. 'url': reverse('social:complete', kwargs={'backend': backend.name}),
  180. }
  181. return render(request, 'misago/socialauth.html', {
  182. 'backend_name': backend_name,
  183. })
  184. @partial
  185. def require_activation(strategy, details, backend, user=None, is_new=False, *args, **kwargs):
  186. if not user:
  187. # Social auth pipeline has entered corrupted state
  188. # Remove partial auth state and redirect user to beginning
  189. partial_token = strategy.session.get('partial_pipeline_token')
  190. if partial_token:
  191. strategy.clean_partial_pipeline(partial_token)
  192. return None
  193. if not user.requires_activation:
  194. return None
  195. request = strategy.request
  196. backend_name = get_social_auth_backend_name(backend.name)
  197. response_data = get_registration_result_json(user)
  198. response_data.update({'step': 'done', 'backend_name': backend_name})
  199. if request.method == 'POST':
  200. # we are carrying on from requestration request
  201. return JsonResponse(response_data)
  202. request.frontend_context['SOCIAL_AUTH'] = response_data
  203. request.frontend_context['SOCIAL_AUTH'].update({
  204. 'url': reverse('social:complete', kwargs={'backend': backend.name}),
  205. })
  206. return render(request, 'misago/socialauth.html', {
  207. 'backend_name': backend_name,
  208. })