support ou selector in backends and forms (fixes #30252)

This commit is contained in:
Benjamin Dauvergne 2019-02-14 12:17:10 +01:00
parent fdc2959104
commit fae901f5a2
5 changed files with 106 additions and 7 deletions

View File

@ -489,7 +489,7 @@ class LDAPBackend(object):
log.debug('got config %r', blocks)
return blocks
def authenticate(self, username=None, password=None, realm=None):
def authenticate(self, username=None, password=None, realm=None, ou=None, request=None):
if username is None or password is None:
return None
@ -500,9 +500,15 @@ class LDAPBackend(object):
if not ldap:
raise ImproperlyConfigured('ldap is not available')
default_ou_slug = get_default_ou().slug
# Now we can try to authenticate
for block in config:
uid = username
# if ou is provided, ignore LDAP server for other OU
if ou:
if ou.slug != (block.get('ou_slug') or default_ou_slug):
continue
if block['limit_to_realm']:
if realm is None and '@' in username:
uid, realm = username.rsplit('@', 1)

View File

@ -39,7 +39,7 @@ class ModelBackend(ModelBackend):
Authenticates against settings.AUTH_USER_MODEL.
"""
def get_query(self, username, realm):
def get_query(self, username, realm=None, ou=None):
UserModel = get_user_model()
username_field = 'username'
queries = []
@ -59,19 +59,22 @@ class ModelBackend(ModelBackend):
**{username_field: upn(username, realm)}))
else:
queries.append(models.Q(**{username_field: upn(username, realm)}))
return six.moves.reduce(models.Q.__or__, queries)
queries = six.moves.reduce(models.Q.__or__, queries)
if ou:
queries &= models.Q(ou=ou)
return queries
def must_reset_password(self, user):
from .. import models
return bool(models.PasswordReset.filter(user=user).count())
def authenticate(self, username=None, password=None, realm=None, **kwargs):
def authenticate(self, username=None, password=None, realm=None, ou=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if not username:
return
query = self.get_query(username, realm)
query = self.get_query(username=username, realm=realm, ou=ou)
users = get_user_queryset().filter(query)
# order by username to make username without realm come before usernames with realms
# i.e. "toto" should come before "toto@example.com"

View File

@ -22,6 +22,8 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import REDIRECT_FIELD_NAME, forms as auth_forms
from django.utils import html
from django.contrib.auth import authenticate
from django_rbac.utils import get_ou_model
from authentic2.utils import lazy_label
@ -230,7 +232,7 @@ class AuthenticationForm(auth_forms.AuthenticationForm):
raise forms.ValidationError(msg)
try:
super(AuthenticationForm, self).clean()
self.clean_authenticate()
except Exception:
if keys:
self.exponential_backoff.failure(*keys)
@ -240,6 +242,25 @@ class AuthenticationForm(auth_forms.AuthenticationForm):
self.exponential_backoff.success(*keys)
return self.cleaned_data
def clean_authenticate(self):
# copied from django.contrib.auth.forms.AuthenticationForm to add support for ou selector
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
ou = self.cleaned_data.get('ou')
if username is not None and password:
self.user_cache = authenticate(username=username, password=password, ou=ou, request=self.request)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
@property
def media(self):
media = super(AuthenticationForm, self).media

View File

@ -715,3 +715,60 @@ def test_set_password(slapd, settings, db):
assert user.check_password(u'àbon')
user2 = authenticate(username=u'etienne.michu', password=u'àbon')
assert user.pk == user2.pk
def test_ou_selector(slapd, settings, app, ou1):
settings.LDAP_AUTH_SETTINGS = [{
'url': [slapd.ldap_url],
'binddn': force_text(DN),
'bindpw': PASS,
'basedn': u'o=ôrga',
'ou_slug': ou1.slug,
'use_tls': False,
}]
settings.A2_LOGIN_FORM_OU_SELECTOR = True
# Check login to the wrong ou does not work
response = app.get('/login/')
response.form.set('username', USERNAME)
response.form.set('password', PASS)
response.form.set('ou', str(get_default_ou().pk))
response = response.form.submit(name='login-password-submit')
assert response.pyquery('.errorlist.nonfield')
assert '_auth_user_id' not in app.session
# Check login to the proper ou works
response = app.get('/login/')
response.form.set('username', USERNAME)
response.form.set('password', PASS)
response.form.set('ou', str(ou1.pk))
response = response.form.submit(name='login-password-submit').follow()
assert '_auth_user_id' in app.session
def test_ou_selector_default_ou(slapd, settings, app, ou1):
settings.LDAP_AUTH_SETTINGS = [{
'url': [slapd.ldap_url],
'binddn': force_text(DN),
'bindpw': PASS,
'basedn': u'o=ôrga',
'use_tls': False,
}]
settings.A2_LOGIN_FORM_OU_SELECTOR = True
# Check login to the wrong ou does not work
response = app.get('/login/')
response.form.set('username', USERNAME)
response.form.set('password', PASS)
response.form.set('ou', str(ou1.pk))
response = response.form.submit(name='login-password-submit')
assert response.pyquery('.errorlist.nonfield')
assert '_auth_user_id' not in app.session
# Check login to the proper ou works
response = app.get('/login/')
response.form.set('username', USERNAME)
response.form.set('password', PASS)
response.form.set('ou', str(get_default_ou().pk))
response = response.form.submit(name='login-password-submit').follow()
assert '_auth_user_id' in app.session

View File

@ -135,7 +135,7 @@ def test_session_remember_me_nok(app, settings, simple_user, freezer):
assert simple_user.first_name not in response
def test_ou_selector(app, settings, simple_user):
def test_ou_selector(app, settings, simple_user, ou1):
settings.A2_LOGIN_FORM_OU_SELECTOR = True
response = app.get('/login/')
# Check selector is here and there are no errors
@ -148,3 +148,15 @@ def test_ou_selector(app, settings, simple_user):
response.form.set('password', simple_user.username)
response = response.form.submit(name='login-password-submit')
assert response.pyquery('.errorlist')
# Check login to the wrong ou do not work
response.form.set('password', simple_user.username)
response.form.set('ou', str(ou1.pk))
response = response.form.submit(name='login-password-submit')
assert not response.pyquery('.errorlist:not(.nonfield)')
assert response.pyquery('.errorlist.nonfield')
assert '_auth_user_id' not in app.session
# Check login to the proper ou works
response.form.set('password', simple_user.username)
response.form.set('ou', str(simple_user.ou.pk))
response = response.form.submit(name='login-password-submit').follow()
assert '_auth_user_id' in app.session