210 lines
7.8 KiB
Python
210 lines
7.8 KiB
Python
from __future__ import absolute_import
|
|
|
|
from django.core.exceptions import ValidationError
|
|
from django.urls import reverse
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from ..account import app_settings as account_settings
|
|
from ..account.adapter import get_adapter as get_account_adapter
|
|
from ..account.app_settings import EmailVerificationMethod
|
|
from ..account.models import EmailAddress
|
|
from ..account.utils import user_email, user_field, user_username
|
|
from ..utils import (
|
|
deserialize_instance,
|
|
email_address_exists,
|
|
import_attribute,
|
|
serialize_instance,
|
|
valid_email_or_none,
|
|
)
|
|
from . import app_settings
|
|
|
|
|
|
class DefaultSocialAccountAdapter(object):
|
|
|
|
error_messages = {
|
|
"email_taken": _(
|
|
"An account already exists with this e-mail address."
|
|
" Please sign in to that account first, then connect"
|
|
" your %s account."
|
|
)
|
|
}
|
|
|
|
def __init__(self, request=None):
|
|
self.request = request
|
|
|
|
def pre_social_login(self, request, sociallogin):
|
|
"""
|
|
Invoked just after a user successfully authenticates via a
|
|
social provider, but before the login is actually processed
|
|
(and before the pre_social_login signal is emitted).
|
|
|
|
You can use this hook to intervene, e.g. abort the login by
|
|
raising an ImmediateHttpResponse
|
|
|
|
Why both an adapter hook and the signal? Intervening in
|
|
e.g. the flow from within a signal handler is bad -- multiple
|
|
handlers may be active and are executed in undetermined order.
|
|
"""
|
|
pass
|
|
|
|
def authentication_error(
|
|
self,
|
|
request,
|
|
provider_id,
|
|
error=None,
|
|
exception=None,
|
|
extra_context=None,
|
|
):
|
|
"""
|
|
Invoked when there is an error in the authentication cycle. In this
|
|
case, pre_social_login will not be reached.
|
|
|
|
You can use this hook to intervene, e.g. redirect to an
|
|
educational flow by raising an ImmediateHttpResponse.
|
|
"""
|
|
pass
|
|
|
|
def new_user(self, request, sociallogin):
|
|
"""
|
|
Instantiates a new User instance.
|
|
"""
|
|
return get_account_adapter().new_user(request)
|
|
|
|
def save_user(self, request, sociallogin, form=None):
|
|
"""
|
|
Saves a newly signed up social login. In case of auto-signup,
|
|
the signup form is not available.
|
|
"""
|
|
u = sociallogin.user
|
|
u.set_unusable_password()
|
|
if form:
|
|
get_account_adapter().save_user(request, u, form)
|
|
else:
|
|
get_account_adapter().populate_username(request, u)
|
|
sociallogin.save(request)
|
|
return u
|
|
|
|
def populate_user(self, request, sociallogin, data):
|
|
"""
|
|
Hook that can be used to further populate the user instance.
|
|
|
|
For convenience, we populate several common fields.
|
|
|
|
Note that the user instance being populated represents a
|
|
suggested User instance that represents the social user that is
|
|
in the process of being logged in.
|
|
|
|
The User instance need not be completely valid and conflict
|
|
free. For example, verifying whether or not the username
|
|
already exists, is not a responsibility.
|
|
"""
|
|
username = data.get("username")
|
|
first_name = data.get("first_name")
|
|
last_name = data.get("last_name")
|
|
email = data.get("email")
|
|
name = data.get("name")
|
|
user = sociallogin.user
|
|
user_username(user, username or "")
|
|
user_email(user, valid_email_or_none(email) or "")
|
|
name_parts = (name or "").partition(" ")
|
|
user_field(user, "first_name", first_name or name_parts[0])
|
|
user_field(user, "last_name", last_name or name_parts[2])
|
|
return user
|
|
|
|
def get_connect_redirect_url(self, request, socialaccount):
|
|
"""
|
|
Returns the default URL to redirect to after successfully
|
|
connecting a social account.
|
|
"""
|
|
assert request.user.is_authenticated
|
|
url = reverse("socialaccount_connections")
|
|
return url
|
|
|
|
def validate_disconnect(self, account, accounts):
|
|
"""
|
|
Validate whether or not the socialaccount account can be
|
|
safely disconnected.
|
|
"""
|
|
if len(accounts) == 1:
|
|
# No usable password would render the local account unusable
|
|
if not account.user.has_usable_password():
|
|
raise ValidationError(_("Your account has no password set up."))
|
|
# No email address, no password reset
|
|
if app_settings.EMAIL_VERIFICATION == EmailVerificationMethod.MANDATORY:
|
|
if not EmailAddress.objects.filter(
|
|
user=account.user, verified=True
|
|
).exists():
|
|
raise ValidationError(
|
|
_("Your account has no verified e-mail address.")
|
|
)
|
|
|
|
def is_auto_signup_allowed(self, request, sociallogin):
|
|
# If email is specified, check for duplicate and if so, no auto signup.
|
|
auto_signup = app_settings.AUTO_SIGNUP
|
|
if auto_signup:
|
|
email = user_email(sociallogin.user)
|
|
# Let's check if auto_signup is really possible...
|
|
if email:
|
|
if account_settings.UNIQUE_EMAIL:
|
|
if email_address_exists(email):
|
|
# Oops, another user already has this address.
|
|
# We cannot simply connect this social account
|
|
# to the existing user. Reason is that the
|
|
# email adress may not be verified, meaning,
|
|
# the user may be a hacker that has added your
|
|
# email address to their account in the hope
|
|
# that you fall in their trap. We cannot
|
|
# check on 'email_address.verified' either,
|
|
# because 'email_address' is not guaranteed to
|
|
# be verified.
|
|
auto_signup = False
|
|
# FIXME: We redirect to signup form -- user will
|
|
# see email address conflict only after posting
|
|
# whereas we detected it here already.
|
|
elif app_settings.EMAIL_REQUIRED:
|
|
# Nope, email is required and we don't have it yet...
|
|
auto_signup = False
|
|
return auto_signup
|
|
|
|
def is_open_for_signup(self, request, sociallogin):
|
|
"""
|
|
Checks whether or not the site is open for signups.
|
|
|
|
Next to simply returning True/False you can also intervene the
|
|
regular flow by raising an ImmediateHttpResponse
|
|
"""
|
|
return get_account_adapter(request).is_open_for_signup(request)
|
|
|
|
def get_signup_form_initial_data(self, sociallogin):
|
|
user = sociallogin.user
|
|
initial = {
|
|
"email": user_email(user) or "",
|
|
"username": user_username(user) or "",
|
|
"first_name": user_field(user, "first_name") or "",
|
|
"last_name": user_field(user, "last_name") or "",
|
|
}
|
|
return initial
|
|
|
|
def deserialize_instance(self, model, data):
|
|
return deserialize_instance(model, data)
|
|
|
|
def serialize_instance(self, instance):
|
|
return serialize_instance(instance)
|
|
|
|
def get_app(self, request, provider):
|
|
# NOTE: Avoid loading models at top due to registry boot...
|
|
from allauth.socialaccount.models import SocialApp
|
|
|
|
config = app_settings.PROVIDERS.get(provider, {}).get("APP")
|
|
if config:
|
|
app = SocialApp(provider=provider)
|
|
for field in ["client_id", "secret", "key", "certificate_key"]:
|
|
setattr(app, field, config.get(field))
|
|
else:
|
|
app = SocialApp.objects.get_current(provider, request)
|
|
return app
|
|
|
|
|
|
def get_adapter(request=None):
|
|
return import_attribute(app_settings.ADAPTER)(request)
|