authenticators: add easy accesible OU based on service's ACL (#36783)

It replaces changes from #35213. OU are added after OU remembered
through cookies; they are ordered based on their user subset's count
(how many of their users can access the targeted service).
This commit is contained in:
Benjamin Dauvergne 2019-10-08 16:46:34 +02:00
parent 1a2cdb39af
commit 94f4ec8c7d
3 changed files with 40 additions and 16 deletions

View File

@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Count
from django.shortcuts import render
from django.utils.translation import ugettext as _, ugettext_lazy
@ -35,6 +36,35 @@ class LoginPasswordAuthenticator(object):
def id(self):
return 'password'
def get_service_ous(self, request):
service_slug = request.GET.get(constants.SERVICE_FIELD_NAME)
if not service_slug:
return []
roles = Role.objects.filter(allowed_services__slug=service_slug).children()
if not roles:
return []
service_ou_ids = []
qs = User.objects.filter(roles__in=roles).values_list('ou').annotate(count=Count('ou')).order_by('-count')
for ou_id, count in qs:
if not ou_id:
continue
service_ou_ids.append(ou_id)
if not service_ou_ids:
return []
return OU.objects.filter(pk__in=service_ou_ids)
def get_preferred_ous(self, request):
preferred_ous_cookie = utils.get_remember_cookie(request, 'preferred-ous')
preferred_ous = []
if preferred_ous_cookie:
preferred_ous.extend(OU.objects.filter(pk__in=preferred_ous_cookie))
# for the special case of services open to only one OU, pre-select it
for ou in self.get_service_ous(request):
if ou in preferred_ous:
continue
preferred_ous.append(ou)
return preferred_ous
def login(self, request, *args, **kwargs):
service_slug = request.GET.get(constants.SERVICE_FIELD_NAME)
context = kwargs.get('context', {})
@ -45,20 +75,9 @@ class LoginPasswordAuthenticator(object):
# Special handling when the form contains an OU selector
if app_settings.A2_LOGIN_FORM_OU_SELECTOR:
default_ou = None
# for the special case of services open to only one OU, pre-select it
if service_slug:
roles = Role.objects.filter(allowed_services__slug=service_slug).children()
ous = OU.objects.filter(id__in=User.objects.filter(roles__in=roles).values_list('ou_id', flat=True))
if len(ous) == 1:
default_ou = ous[0]
preferred_ous = utils.get_remember_cookie(request, 'preferred-ous')
preferred_ous = self.get_preferred_ous(request)
if preferred_ous:
preferred_ous = OU.objects.filter(pk__in=preferred_ous)
if not default_ou and preferred_ous:
default_ou = preferred_ous[0]
if default_ou:
initial['ou'] = default_ou
initial['ou'] = preferred_ous[0]
form = authentication_forms.AuthenticationForm(
request=request,

View File

@ -58,10 +58,10 @@ class AuthenticationForm(auth_forms.AuthenticationForm):
else:
if preferred_ous:
choices = self.fields['ou'].choices
new_choices = [
new_choices = list(choices)[:1] + [
(ugettext('Preferred organizational units'), [
(ou.pk, ou.name) for ou in preferred_ous]),
(ugettext('All organizational units'), list(choices)),
(ugettext('All organizational units'), list(choices)[1:]),
]
self.fields['ou'].choices = new_choices

View File

@ -189,9 +189,14 @@ def test_ou_selector(app, settings, simple_user, ou1, ou2, user_ou1, role_ou1):
response = app.get('/login/?service=service')
assert response.pyquery.find('select#id_ou option[selected]')[0].text == u'Default organizational unit'
# user is added to role_ou1, now the OU of the only user is selected
# user is added to role_ou1, default for user is still selected
user_ou1.roles.add(role_ou1)
response = app.get('/login/?service=service')
assert response.pyquery.find('select#id_ou option[selected]')[0].text == u'Default organizational unit'
# Clear cookies, OU1 is selected
app.cookiejar.clear()
response = app.get('/login/?service=service')
assert response.pyquery.find('select#id_ou option[selected]')[0].text == u'OU1'
# if we change the user's ou, then default selected OU changes