support ou selector in backends and forms (fixes #30252)
This commit is contained in:
parent
fdc2959104
commit
fae901f5a2
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue