-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: ✨ social-auth auto associate existing user #458
Open
goztrk
wants to merge
1
commit into
hacktoolkit:master
Choose a base branch
from
BawsHuman:features/social-auth-auto-associate
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,32 @@ | ||
# Third Party / PIP Imports | ||
# Django Imports | ||
from django.shortcuts import redirect | ||
|
||
# Third Party (PyPI) Imports | ||
# Django Extensions Imports | ||
# Third Party (PyPI) Imports | ||
from social_core.pipeline.partial import partial | ||
from social_core.pipeline.social_auth import associate_user | ||
|
||
# Django Imports | ||
from django.shortcuts import redirect | ||
|
||
# HTK Imports | ||
from htk.apps.accounts.emails import welcome_email | ||
from htk.apps.accounts.session_keys import * | ||
from htk.apps.accounts.utils import associate_user_email | ||
from htk.apps.accounts.utils import get_incomplete_signup_user_by_email | ||
from htk.apps.accounts.utils import get_user_by_email | ||
from htk.apps.accounts.view_helpers import redirect_to_social_auth_complete | ||
from htk.apps.accounts.session_keys import ( | ||
SOCIAL_AUTH_FLOW_KEYS, | ||
SOCIAL_REGISTRATION_SETTING_AGREED_TO_TERMS, | ||
SOCIAL_REGISTRATION_SETTING_EMAIL, | ||
SOCIAL_REGISTRATION_SETTING_MISSING_EMAIL, | ||
) | ||
from htk.apps.accounts.utils import ( | ||
associate_user_email, | ||
get_incomplete_signup_user_by_email, | ||
get_user_by_email, | ||
) | ||
from htk.utils import htk_setting | ||
|
||
|
||
# isort: off | ||
|
||
|
||
# Custom Pipeline Functions | ||
# https://django-social-auth.readthedocs.org/en/v0.7.22/pipeline.html | ||
# | ||
|
@@ -28,99 +40,148 @@ | |
|
||
# 1. If there is no email, have the user enter an email | ||
# 2. Check association. If there is an account with that email: | ||
# a. "An account with this email address already exists. Please log in to link your {{ SOCIAL }} account." | ||
# b. "An account with this email address is already linked to {{ SOCIAL }}. Please create a new account using a different email address." | ||
# a. "An account with this email address already exists. Please log in to link your | ||
# {{ SOCIAL }} account." | ||
# b. "An account with this email address is already linked to {{ SOCIAL }}. Please | ||
# create a new account using a different email address." | ||
# 3. Create the account with the username and email | ||
|
||
|
||
def python_social_auth_shim(pipeline_func): | ||
"""Shim layer decorator for django-social-auth to python-social auth migration | ||
pipeline complete wasn't passing the request object, but the strategy object instead | ||
""" | ||
|
||
def wrapped(strategy, *args, **kwargs): | ||
if not kwargs.get('request'): | ||
request = strategy.request | ||
kwargs['request'] = request | ||
return pipeline_func(*args, **kwargs) | ||
|
||
return wrapped | ||
|
||
|
||
def reset_session_keys(strategy, *args, **kwargs): | ||
"""Reset a bunch of keys used as part of the social auth flow | ||
This is to prevent partially-completed values from a previous flow from affecting a new social auth flow | ||
|
||
This is to prevent partially-completed values from a previous flow from affecting a | ||
new social auth flow | ||
""" | ||
for key in SOCIAL_AUTH_FLOW_KEYS: | ||
if strategy.request.session.get(key): | ||
del strategy.request.session[key] | ||
return None | ||
|
||
|
||
@partial | ||
def check_email(strategy, details, user=None, *args, **kwargs): | ||
def check_email(strategy, details, backend, uid, user=None, *args, **kwargs): | ||
"""Ask the user to enter the email if we don't have one yet | ||
|
||
The pipeline process was cut prior to this custom pipeline function, and will resume to this same function after completing | ||
The pipeline process was cut prior to this custom pipeline function, and will | ||
resume to this same function after completing the social auth flow. | ||
""" | ||
response = None | ||
if user is None: | ||
strategy.request.session['backend'] = kwargs.get('current_partial').backend | ||
strategy.request.session['backend'] = kwargs.get( | ||
'current_partial' | ||
).backend | ||
social_email = details.get('email') | ||
collected_email = strategy.request.session.get(SOCIAL_REGISTRATION_SETTING_EMAIL) | ||
collected_email = strategy.request.session.get( | ||
SOCIAL_REGISTRATION_SETTING_EMAIL | ||
) | ||
if social_email: | ||
# email available from social auth | ||
user = get_user_by_email(social_email) | ||
if user and user.is_active: | ||
# a user is already associated with this email | ||
# TODO: there is an error with linking accounts... | ||
strategy.request.session[SOCIAL_REGISTRATION_SETTING_EMAIL] = social_email | ||
if user.has_usable_password(): | ||
# user should log into the existing account with a password | ||
url_name = htk_setting('HTK_ACCOUNTS_REGISTER_SOCIAL_LOGIN_URL_NAME') | ||
# A user is already associated with this email | ||
auto_associate_backends = htk_setting( | ||
'HTK_ACCOUNTS_SOCIAL_AUTO_ASSOCIATE_BACKENDS', [] | ||
) | ||
backend_name = backend.name | ||
if backend_name in auto_associate_backends: | ||
# The backend is one of the auto-associate backends, so we need to | ||
# associate the Django user with the social auth account | ||
# This is a job for `associate_user` pipeline but the association | ||
# must happen in here. | ||
associate_user(backend, uid, user=user) | ||
Comment on lines
+96
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reviewer's NoteThe main change! |
||
else: | ||
# no password was set, so user must log in with another social auth account | ||
url_name = htk_setting('HTK_ACCOUNTS_REGISTER_SOCIAL_ALREADY_LINKED_URL_NAME') | ||
response = redirect(url_name) | ||
# TODO: There is an error with linking accounts... | ||
strategy.request.session[ | ||
SOCIAL_REGISTRATION_SETTING_EMAIL | ||
] = social_email | ||
if user.has_usable_password(): | ||
# user should log into the existing account with a password | ||
url_name = htk_setting( | ||
'HTK_ACCOUNTS_REGISTER_SOCIAL_LOGIN_URL_NAME' | ||
) | ||
response = redirect(url_name) | ||
else: | ||
# no password was set, so user must log in with another social | ||
# auth account | ||
url_name = htk_setting( | ||
'HTK_ACCOUNTS_REGISTER_SOCIAL_ALREADY_LINKED_URL_NAME' | ||
) | ||
response = redirect(url_name) | ||
else: | ||
# no user found with this email | ||
pass | ||
|
||
elif collected_email: | ||
# email provided by user | ||
details['email'] = collected_email | ||
response = { 'details' : details } | ||
response = {'details': details} | ||
else: | ||
# no email provided from social auth | ||
strategy.request.session[SOCIAL_REGISTRATION_SETTING_MISSING_EMAIL] = True | ||
url_name = htk_setting('HTK_ACCOUNTS_REGISTER_SOCIAL_EMAIL_URL_NAME') | ||
strategy.request.session[ | ||
SOCIAL_REGISTRATION_SETTING_MISSING_EMAIL | ||
] = True | ||
url_name = htk_setting( | ||
'HTK_ACCOUNTS_REGISTER_SOCIAL_EMAIL_URL_NAME' | ||
) | ||
response = redirect(url_name) | ||
else: | ||
pass | ||
|
||
return response | ||
|
||
|
||
@partial | ||
def check_terms_agreement(strategy, details, user=None, *args, **kwargs): | ||
""" | ||
Ask the user to agree to Privacy Policy and Terms of Service | ||
""" | ||
response = None | ||
if user is None: | ||
agreed_to_terms = strategy.request.session.get(SOCIAL_REGISTRATION_SETTING_AGREED_TO_TERMS, False) | ||
agreed_to_terms = strategy.request.session.get( | ||
SOCIAL_REGISTRATION_SETTING_AGREED_TO_TERMS, False | ||
) | ||
if not agreed_to_terms: | ||
email = details.get('email') | ||
strategy.request.session[SOCIAL_REGISTRATION_SETTING_EMAIL] = email | ||
url_name = htk_setting('HTK_ACCOUNTS_REGISTER_SOCIAL_EMAIL_AND_TERMS_URL_NAME') | ||
url_name = htk_setting( | ||
'HTK_ACCOUNTS_REGISTER_SOCIAL_EMAIL_AND_TERMS_URL_NAME' | ||
) | ||
response = redirect(url_name) | ||
else: | ||
pass | ||
else: | ||
pass | ||
return response | ||
|
||
|
||
def check_incomplete_signup(strategy, details, user=None, *args, **kwargs): | ||
"""Checks for an incomplete signup, and sets that User instead | ||
""" | ||
"""Checks for an incomplete signup, and sets that User instead""" | ||
response = None | ||
if user is None: | ||
social_email = details.get('email') | ||
user = get_incomplete_signup_user_by_email(social_email) | ||
response = { | ||
'user' : user, | ||
'is_new' : user is None, | ||
'user': user, | ||
'is_new': user is None, | ||
} | ||
return response | ||
|
||
|
||
def set_username(strategy, details, user, social, *args, **kwargs): | ||
"""This pipeline function can be used to set UserProfile.has_username_set = True | ||
|
||
|
@@ -137,9 +198,9 @@ def set_username(strategy, details, user, social, *args, **kwargs): | |
user_profile.save() | ||
return response | ||
|
||
|
||
def associate_email(strategy, details, user, social, *args, **kwargs): | ||
"""Associate email with the user | ||
""" | ||
"""Associate email with the user""" | ||
if not user or not social: | ||
return None | ||
|
||
|
@@ -149,26 +210,34 @@ def associate_email(strategy, details, user, social, *args, **kwargs): | |
domain = strategy.request.get_host() | ||
# Should confirm if the email was provided by the social auth provider, not the user | ||
# i.e. SOCIAL_REGISTRATION_SETTING_MISSING_EMAIL was False | ||
confirmed = not(strategy.request.session.get(SOCIAL_REGISTRATION_SETTING_MISSING_EMAIL, False)) | ||
user_email = associate_user_email(user, email, domain=domain, confirmed=confirmed) | ||
confirmed = not ( | ||
strategy.request.session.get( | ||
SOCIAL_REGISTRATION_SETTING_MISSING_EMAIL, False | ||
) | ||
) | ||
user_email = associate_user_email( | ||
user, email, domain=domain, confirmed=confirmed | ||
) | ||
|
||
if user_email: | ||
# need to update the User with the activated one, so that it doesn't get overwritten later on | ||
# need to update the User with the activated one, so that it doesn't get | ||
# overwritten later on | ||
response = { | ||
'user': user_email.user, | ||
} | ||
return response | ||
|
||
|
||
def handle_new_user(user, is_new, *args, **kwargs): | ||
"""Do stuff if the account was newly created | ||
""" | ||
"""Do stuff if the account was newly created""" | ||
if not user: | ||
return None | ||
|
||
if is_new: | ||
# send a welcome email to the user, regardless of email confirmation status | ||
welcome_email(user) | ||
|
||
|
||
def post_connect(user, social, *args, **kwargs): | ||
response = None | ||
return response |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewer's Note
Most of this is formatting. The important part is marked with a comment.