Refs #2465 -- Introduce unique identifiers for authentication backends (#2466)

This commit is contained in:
Raphael Michel
2022-02-23 10:16:35 +01:00
committed by GitHub
parent 2bf5a0ce8a
commit d7acd2b6bf
6 changed files with 221 additions and 10 deletions

View File

@@ -20,20 +20,31 @@ Basically, three pre-defined flows are supported:
* Authentication mechanisms that rely on **redirection**, e.g. to an OAuth provider. These can be implemented by
supplying a ``authentication_url`` method and implementing a custom return view.
Authentication backends are *not* collected through a signal. Instead, they must explicitly be set through the
``auth_backends`` directive in the ``pretix.cfg`` :ref:`configuration file <config>`.
For security reasons, authentication backends are *not* automatically discovered through a signal. Instead, they must
explicitly be set through the ``auth_backends`` directive in the ``pretix.cfg`` :ref:`configuration file <config>`.
In each of these methods (``form_authenticate``, ``request_authenticate`` or your custom view) you are supposed to
either get an existing :py:class:`pretix.base.models.User` object from the database or create a new one. There are a
few rules you need to follow:
In each of these methods (``form_authenticate``, ``request_authenticate``, or your custom view) you are supposed to
use ``User.objects.get_or_create_for_backend`` to get a :py:class:`pretix.base.models.User` object from the database
or create a new one.
* You **MUST** only return users with the ``auth_backend`` attribute set to the ``identifier`` value of your backend.
There are a few rules you need to follow:
* You **MUST** create new users with the ``auth_backend`` attribute set to the ``identifier`` value of your backend.
* You **MUST** have some kind of identifier for a user that is globally unique and **SHOULD** never change, even if the
user's name or email address changes. This could e.g. be the ID of the user in an external database. The identifier
must not be longer than 190 characters. If you worry your backend might generated longer identifiers, consider
using a hash function to trim them to a constant length.
* You **SHOULD** not allow users created by other authentication backends to log in through your code, and you **MUST**
only create, modify or return users with ``auth_backend`` set to your backend.
* Every user object **MUST** have an email address. Email addresses are globally unique. If the email address is
already registered to a user who signs in through a different backend, you **SHOULD** refuse the login.
``User.objects.get_or_create_for_backend`` will follow these rules for you automatically. It works like this:
.. autoclass:: pretix.base.models.UserManager
:members: get_or_create_for_backend
The backend interface
---------------------
@@ -59,6 +70,7 @@ The backend interface
.. automethod:: authentication_url
Logging users in
----------------
@@ -68,3 +80,45 @@ recommend that you use the following utility method to correctly set session val
authentication (if activated):
.. autofunction:: pretix.control.views.auth.process_login
A custom view that is called after a redirect from an external identity provider could look like this::
from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
from pretix.base.models import User
from pretix.base.models.auth import EmailAddressTakenError
from pretix.control.views.auth import process_login
def return_view(request):
# Verify validity of login with the external provider's API
api_response = my_verify_login_function(
code=request.GET.get('code')
)
try:
u = User.objects.get_or_create_for_backend(
'my_backend_name',
api_response['userid'],
api_response['email'],
set_always={
'fullname': '{} {}'.format(
api_response.get('given_name', ''),
api_response.get('family_name', ''),
),
},
set_on_creation={
'locale': api_response.get('locale').lower()[:2],
'timezone': api_response.get('zoneinfo', 'UTC'),
}
)
except EmailAddressTakenError:
messages.error(
request, _('We cannot create your user account as a user account in this system '
'already exists with the same email address.')
)
return redirect(reverse('control:auth.login'))
else:
return process_login(request, u, keep_logged_in=False)