Home > Enterprise >  Associate user by email
Associate user by email

Time:02-27

I want to override the pipeline to associate user's with their email for accounts that are only active. But I need the backend for a regular login. The AUTHENTICATION_BACKENDS(django.contrib.auth.backends.AllowAllUsersModelBackend) allows for all user's to be authenticated but I want only certain user's with the is_active to be authenticated using Google Login.

settings.py

AUTHENTICATION_BACKENDS = ['social_core.backends.google.GoogleOAuth2','django.contrib.auth.backends.AllowAllUsersModelBackend',]
# Extends default user with additional fields 
AUTH_USER_MODEL = 'pages.Profile' 
SOCIAL_AUTH_USER_MODEL = 'pages.Profile' 

# social auth configs for google
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = config('GOOGLE_OAUTH2_KEY')
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = config('GOOGLE_OAUTH2_SECRET')
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['https://www.googleapis.com/auth/calendar']
SOCIAL_AUTH_JSONFIELD_ENABLED = True
SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {'access_type': 'offline','approval_prompt':'force'}
SESSION_COOKIE_SAMESITE = None
SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.auth_allowed',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    'social_core.pipeline.social_auth.associate_by_email',  # <--- enable this one
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
    'pages.pipeline.save_token'
) 

views.py

def login(request):
    if request.method == 'POST':
        form = AuthenticationForm(request.POST)
        username = request.POST['username']
        password = request.POST['password']
        user = authenticate(username=username, password=password)
        if user:
            if user.is_active:
                auth_login(request, user)
                return redirect('home')
            else:
                messages.error(request,'User blocked',extra_tags='login')
                return redirect('login')
        else:
            messages.error(request,'username or password not correct',extra_tags='login')
            return redirect('login')
    else:
        form = AuthenticationForm()
    return render(request, 'registration/login.html',{'form':form})

CodePudding user response:

To override the original partial,

In your pipeline.py (create one, if you don't have it in your app directory), define a method:

from social_core.pipeline.partial import partial
from social_core.exceptions import AuthAlreadyAssociated, AuthException, AuthForbidden


@partial
def associate_by_email(backend, details, user=None, *args, **kwargs):
    # No user 
    if user:
        return None

    email = details.get('email')
    if email:
        # Try to associate accounts registered with the same email address,
        # only if it's a single object. AuthException is raised if multiple
        # objects are returned.
        users = list(backend.strategy.storage.user.get_users_by_email(email))

        #That's the line you want to add
        active_users = [user for user in users if user.is_active]

        if len(active_users) == 0:
            return None
        elif len(active_users) > 1:
                raise AuthException(
                    backend,
                    'The given email address is associated with another account'
                )
        else:
            return {'user': active_users[0],
                    'is_new': False}

And then in your settings.py replace the line

SOCIAL_AUTH_PIPELINE = (
    ...
    'social_core.pipeline.social_auth.associate_by_email',  # <--- enable this
    ...
)

with:

SOCIAL_AUTH_PIPELINE = (
    ...
    'your_app.pipeline.associate_by_email',  # <--- enable this one
    ...
)

Sure position the pipeline partial where it has in place of original one. The original method does not perform the check, so to add this behavior you have to write your own partial that overrides this behavior. Assuming you have a Django app (here for the sake of simplicity called your_app - just update the names accordingly. The original code for partial associate_by_email can be found here.

If you want to add this check to simple authentication, you can provide your own backend in a file your_app/backends.py:

from django.core.exceptions import ObjectDoesNotExist
from settings import settings
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend

class LocalActiveBackend(object):
    def authenticate(self, username=None, password=None):
        try:
            # Add extra check for is_active
            user = User.objects.get(username=username, is_active=True)
        except User.DoesNotExist:
            return None

        pwd_valid = user.check_password(password)
    
        if pwd_valid:
            return user

        return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

And then append that new backend to the list of available backends:

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend', # default

    ...

    'your_app.backends.LocalActiveBackend',
)

This will allow you using it with simple authentication by password and username if needed.

  • Related