[PYTHON] How to update user information when logging in to Django RemoteUserMiddleware

background

Use RemoteUserMiddleware to do SSO with Django.

On the Django app side, the environment variable REMOTE_USER set by the SAML module (Shibboleth in the own environment) is used as ID information to identify the user, but at this time, attributes other than the ID information (for example, email) are also included in the User class on the Django app side. I want to capture it.

Related article: Django + Shibboleth with RemoteUserMiddleware (Explanation of introduction of RemoteUserMiddleware)

environment

--Windows Server 2016 (not directly related) --Apache 2.4 (not directly related) --Python 3.7.7 (not directly related)

Prerequisite knowledge

RemoteUserBackend searches for an object whose username has the environment variable REMOTE_USER from the User model specified by settings.AUTH_USER_MODEL. If found, log in as that user. If not found, create a User object with REMOTE_USER as username and log in (this setting can be changed).

Step 1: Override configure_user

Originally, RemoteUserBackend uses the method for manipulating the User object when creating the User object on the Django application side.

python:django.contrib.auth.backends


class RemoteUserBackend(ModelBackend):
...
    def configure_user(self, request, user):
        """
        Configure a user after creation and return the updated user.

        By default, return the user unmodified.
        """
        return user

Create backends.py (any name) in a suitable app.

appname
└── appname
    ├── templates
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    ├── views.py
    ├── wsgi.py
    └── backends.py  #← Create

Override configure_user. This time, I decided to input the attribute ʻATR_mail` added by Shibboleth to the HTTP Header in the User object.

backends.py


from django.contrib.auth.backends import RemoteUserBackend

class MyRemoteUserBackend(RemoteUserBackend):
    def configure_user(self, request, user: MyUser):
        user.email = request.META['ATR_mail']
        user.save() #← Don't forget! !!
        return user

To enable this, you need to change ʻAUTHENTICATION_BACKENDS in settings.py`.

settings.py


AUTHENTICATION_BACKENDS = (
    # 'django.contrib.auth.backends.RemoteUserBackend', #← Delete
    'appname.backends.MyRemoteUserBackend', #← Add
    'django.contrib.auth.backends.ModelBackend',
)

Step 2: Override ʻauthenticate`

Overriding configure_user will only execute configure_user if you create a new User object for Django's first-time REMOTE_USER and log in. ** **

Even if the attribute information on the IdP side changes, the existing User object is not updated, but this seems to be inconvenient in many cases. For example, if you are using SSO in a company and the department or extension number changes from time to time.

In that case, you have to modify ʻauthenticate. Fortunately, you can use the MyRemoteUserBackend of backends.pycreated earlier. Most are copies of the superclassRemoteUserBackend ʻauthenticate.

backends.py


from django.contrib.auth.backends import RemoteUserBackend

from django.contrib.auth.models import User

import inspect
import warnings

from django.contrib.auth import get_user_model
from django.utils.deprecation import RemovedInDjango31Warning

UserModel = get_user_model()

class MyRemoteUserBackend(RemoteUserBackend):

    def authenticate(self, request, remote_user):
        """
        The username passed as ``remote_user`` is considered trusted. Return
        the ``User`` object with the given username. Create a new ``User``
        object if ``create_unknown_user`` is ``True``.

        Return None if ``create_unknown_user`` is ``False`` and a ``User``
        object with the given username is not found in the database.
        """
        if not remote_user:
            return
        user = None
        username = self.clean_username(remote_user)

        # Note that this could be accomplished in one try-except clause, but
        # instead we use get_or_create when creating unknown users since it has
        # built-in safeguards for multiple threads.
        if self.create_unknown_user:
            user, created = UserModel._default_manager.get_or_create(**{
                UserModel.USERNAME_FIELD: username
            })
            if created:                                      #← Attention
                args = (request, user)
                try:
                    inspect.getcallargs(self.configure_user, request, user)
                except TypeError:
                    args = (user,)
                    warnings.warn(
                        'Update %s.configure_user() to accept `request` as '
                        'the first argument.'
                        % self.__class__.__name__, RemovedInDjango31Warning
                    )
                user = self.configure_user(*args)            #← Attention
            else:                                            #← Add
                user = self.configure_user(request, user)    #← Add
        else:
            try:
                user = UserModel._default_manager.get_by_natural_key(username)
            except UserModel.DoesNotExist:
                pass
        return user if self.user_can_authenticate(user) else None

    def configure_user(self, request, user: User):
        user.email = request.META['ATR_mail']
        user.save()
        return user

As stated in "Attention", configure_user is originally called only when created == True in ʻUserModel._default_manager.get_or_create`. This does not allow you to update existing user information.

Therefore, add two lines of "add" so that configure_user is called even when created! = True. If you want to separate the operation for creating a new User object and updating it, you can imitate configure_user and create a new ʻupdate_user` etc. so that it will be called.

that's all.

Recommended Posts

How to update user information when logging in to Django RemoteUserMiddleware
Pass login user information to view in Django
How to update Spyder in Anaconda
How to reflect CSS in Django
How to output additional information when logging with python's logging module
How to delete expired sessions in Django
How to do Server-Sent Events in Django
How to convert DateTimeField format in Django
Output user information etc. to Django log
How to implement Rails helper-like functionality in Django
[Django] How to resolve errors when installing mysqlclient
How to reflect ImageField in Django + Docker (pillow)
How to run some script regularly in Django
How to create a Rest Api in Django
How to hide user input in PySimple GUI
[Django memo] I want to set the login user information in the form in advance
How to update easy_install
How to update Spyder
update django version 1.11.1 to 2.2
How to get multiple model objects randomly in Django
How to access with cache when reading_json in pandas
How to use bootstrap in Django generic class view
How to exit when using Python in Terminal (Mac)
How to upload files in Django generic class view
How to use Decorator in Django and how to make it
How to reference static files in a Django project
How to use fixture in Django to populate sample data associated with a user model
How to write custom validations in the Django REST Framework
How to handle static files when deploying to production with Django
How to use Laravel-like ORM / query builder Orator in Django
How to update with SQLAlchemy?
[Django] How to give input values in advance with ModelForm
How to resolve CSRF Protection when using AngularJS with Django
How to generate a query using the IN operator in Django
How to specify command line arguments when debugging in PyCharm
What to do when "Invalid HTTP_HOST header" appears in Django
In Django, how to abbreviate the long displayed string as ....
How to develop in Python
Change the message displayed when logging in to Raspberry Pi
How to not escape Japanese when dealing with json in python
Automatically acquire the operation log in the terminal when logging in to Linux
[Linux] I want to know the date when the user logged in
How to display formulas in latex when using sympy (> = 1.4) in Google Colaboratory
[Django] How to read variables / constants defined in an external file
How to deploy a Django app on heroku in just 5 minutes
How to improve when Spyder's editor is very heavy in Mavericks
[Tips] How to do template extends when creating HTML with django
[Python] How to do PCA in Python
How to handle session in SQLAlchemy
[Django] How to test Form [TDD]
How to use classes in Theano
How to write soberly in pandas
How to collect images in Python
Errors related to memcached in django
How to use SQLite in Python
How to use Python's logging module
How to create an email user
How to add sudo when debugging
How to convert 0.5 to 1056964608 in one shot
How to get started with Django
How to kill processes in bulk