Projet

Général

Profil

0002-authenticators-migrate-login-password-authenticator-.patch

Valentin Deniaud, 17 mai 2022 17:21

Télécharger (35,6 ko)

Voir les différences:

Subject: [PATCH 2/3] authenticators: migrate login password authenticator
 (#53902)

 src/authentic2/apps/authenticators/forms.py   |  24 +++-
 .../0002_loginpasswordauthenticator.py        |  47 ++++++
 .../migrations/0003_auto_20220413_1504.py     |  33 +++++
 src/authentic2/apps/authenticators/models.py  |  37 +++++
 .../authenticators/authenticator_detail.html  |   4 +-
 src/authentic2/apps/authenticators/views.py   |   6 +
 src/authentic2/authenticators.py              | 134 ------------------
 src/authentic2/forms/authentication.py        |   7 +-
 src/authentic2/settings.py                    |   2 +-
 src/authentic2/utils/misc.py                  |  11 +-
 src/authentic2/views.py                       | 105 ++++++++++++++
 tests/auth_fc/test_auth_fc.py                 |   5 +-
 tests/test_auth_oidc.py                       |   5 +-
 tests/test_auth_saml.py                       |   5 +-
 tests/test_ldap.py                            |   5 +-
 tests/test_login.py                           |  17 +--
 tests/test_manager_authenticators.py          |  47 ++++++
 17 files changed, 338 insertions(+), 156 deletions(-)
 create mode 100644 src/authentic2/apps/authenticators/migrations/0002_loginpasswordauthenticator.py
 create mode 100644 src/authentic2/apps/authenticators/migrations/0003_auto_20220413_1504.py
src/authentic2/apps/authenticators/forms.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
from django import forms
18
from django.core.exceptions import ValidationError
19
from django.template import Template, TemplateSyntaxError, VariableDoesNotExist
20
from django.utils.translation import ugettext as _
18 21

  
19 22
from authentic2.forms.mixins import SlugMixin
20 23

  
21
from .models import BaseAuthenticator
24
from .models import BaseAuthenticator, LoginPasswordAuthenticator
25

  
26

  
27
class AuthenticatorFormMixin:
28
    def clean_show_condition(self):
29
        condition = self.cleaned_data['show_condition']
30
        if condition:
31
            try:
32
                Template('{%% if %s %%}OK{%% endif %%}' % condition)
33
            except (TemplateSyntaxError, VariableDoesNotExist) as e:
34
                raise ValidationError(_('template syntax error: %s') % e)
35
        return condition
22 36

  
23 37

  
24 38
class AuthenticatorAddForm(SlugMixin, forms.ModelForm):
25 39
    field_order = ('authenticator', 'name', 'ou')
26
    authenticators = {x.type: x for x in BaseAuthenticator.__subclasses__()}
40
    authenticators = {x.type: x for x in BaseAuthenticator.__subclasses__() if not x.internal}
27 41

  
28 42
    authenticator = forms.ChoiceField(choices=[(k, v._meta.verbose_name) for k, v in authenticators.items()])
29 43

  
......
35 49
        Authenticator = self.authenticators[self.cleaned_data['authenticator']]
36 50
        self.instance = Authenticator(name=self.cleaned_data['name'], ou=self.cleaned_data['ou'])
37 51
        return super().save()
52

  
53

  
54
class LoginPasswordAuthenticatorEditForm(AuthenticatorFormMixin, forms.ModelForm):
55
    class Meta:
56
        model = LoginPasswordAuthenticator
57
        exclude = ('name', 'slug', 'ou')
src/authentic2/apps/authenticators/migrations/0002_loginpasswordauthenticator.py
1
# Generated by Django 2.2.28 on 2022-04-13 12:56
2

  
3
import django.db.models.deletion
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('authenticators', '0001_initial'),
11
    ]
12

  
13
    operations = [
14
        migrations.CreateModel(
15
            name='LoginPasswordAuthenticator',
16
            fields=[
17
                (
18
                    'baseauthenticator_ptr',
19
                    models.OneToOneField(
20
                        auto_created=True,
21
                        on_delete=django.db.models.deletion.CASCADE,
22
                        parent_link=True,
23
                        primary_key=True,
24
                        serialize=False,
25
                        to='authenticators.BaseAuthenticator',
26
                    ),
27
                ),
28
                (
29
                    'remember_me',
30
                    models.PositiveIntegerField(
31
                        blank=True,
32
                        help_text='Session duration as seconds when using the remember me checkbox. Leave blank to hide the checkbox.',
33
                        null=True,
34
                        verbose_name='Remember me duration',
35
                    ),
36
                ),
37
                (
38
                    'include_ou_selector',
39
                    models.BooleanField(default=False, verbose_name='Include OU selector in login form'),
40
                ),
41
            ],
42
            options={
43
                'verbose_name': 'Password',
44
            },
45
            bases=('authenticators.baseauthenticator',),
46
        ),
47
    ]
src/authentic2/apps/authenticators/migrations/0003_auto_20220413_1504.py
1
# Generated by Django 2.2.28 on 2022-04-13 13:04
2

  
3
from django.db import migrations
4

  
5
from authentic2 import app_settings
6

  
7

  
8
def create_login_password_authenticator(apps, schema_editor):
9
    kwargs_settings = getattr(app_settings, 'AUTH_FRONTENDS_KWARGS', {})
10
    password_settings = kwargs_settings.get('password', {})
11

  
12
    LoginPasswordAuthenticator = apps.get_model('authenticators', 'LoginPasswordAuthenticator')
13
    LoginPasswordAuthenticator.objects.get_or_create(
14
        slug='password-authenticator',
15
        defaults={
16
            'order': password_settings.get('priority', 0),
17
            'show_condition': password_settings.get('show_condition', ''),
18
            'enabled': app_settings.A2_AUTH_PASSWORD_ENABLE,
19
            'remember_me': app_settings.A2_USER_REMEMBER_ME,
20
            'include_ou_selector': app_settings.A2_LOGIN_FORM_OU_SELECTOR,
21
        },
22
    )
23

  
24

  
25
class Migration(migrations.Migration):
26

  
27
    dependencies = [
28
        ('authenticators', '0002_loginpasswordauthenticator'),
29
    ]
30

  
31
    operations = [
32
        migrations.RunPython(create_login_password_authenticator, reverse_code=migrations.RunPython.noop),
33
    ]
src/authentic2/apps/authenticators/models.py
23 23
from django.utils.formats import date_format
24 24
from django.utils.translation import ugettext_lazy as _
25 25

  
26
from authentic2 import views
26 27
from authentic2.utils.evaluate import evaluate_condition
27 28

  
28 29
from .query import AuthenticatorManager
......
60 61

  
61 62
    type = ''
62 63
    manager_form_class = None
64
    internal = False
63 65
    description_fields = ['show_condition']
64 66

  
65 67
    class Meta:
......
106 108
        except Exception as e:
107 109
            logger.error(e)
108 110
            return False
111

  
112

  
113
class LoginPasswordAuthenticator(BaseAuthenticator):
114
    remember_me = models.PositiveIntegerField(
115
        _('Remember me duration'),
116
        blank=True,
117
        null=True,
118
        help_text=_(
119
            'Session duration as seconds when using the remember me checkbox. Leave blank to hide the checkbox.'
120
        ),
121
    )
122
    include_ou_selector = models.BooleanField(_('Include OU selector in login form'), default=False)
123

  
124
    type = 'password'
125
    how = ['password', 'password-on-https']
126
    internal = True
127

  
128
    class Meta:
129
        verbose_name = _('Password')
130

  
131
    @property
132
    def manager_form_class(self):
133
        from .forms import LoginPasswordAuthenticatorEditForm
134

  
135
        return LoginPasswordAuthenticatorEditForm
136

  
137
    def login(self, request, *args, **kwargs):
138
        return views.login_password_login(request, self, *args, **kwargs)
139

  
140
    def profile(self, request, *args, **kwargs):
141
        return views.login_password_profile(request, *args, **kwargs)
142

  
143
    def registration(self, request, *args, **kwargs):
144
        context = kwargs.get('context', {})
145
        return render(request, 'authentic2/login_password_registration_form.html', context)
src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_detail.html
9 9
    <a href="{% url 'a2-manager-authenticator-toggle' pk=object.pk %}">{{ object.enabled|yesno:_("Disable,Enable") }}</a>
10 10
    <a href="{% url 'a2-manager-authenticator-edit' pk=object.pk %}">{% trans "Edit" %}</a>
11 11
    <ul class="extra-actions-menu">
12
      <li><a rel="popup" href="{% url 'a2-manager-authenticator-delete' pk=object.pk %}">{% trans "Delete" %}</a></li>
12
      {% if not object.internal %}
13
        <li><a rel="popup" href="{% url 'a2-manager-authenticator-delete' pk=object.pk %}">{% trans "Delete" %}</a></li>
14
      {% endif %}
13 15
    </ul>
14 16
  </span>
15 17
{% endblock %}
src/authentic2/apps/authenticators/views.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
from django.contrib import messages
18
from django.core.exceptions import PermissionDenied
18 19
from django.http import HttpResponseRedirect
19 20
from django.urls import reverse_lazy
20 21
from django.utils.translation import ugettext as _
......
79 80
    model = BaseAuthenticator
80 81
    success_url = reverse_lazy('a2-manager-authenticators')
81 82

  
83
    def dispatch(self, *args, **kwargs):
84
        if self.get_object().internal:
85
            raise PermissionDenied
86
        return super().dispatch(*args, **kwargs)
87

  
82 88

  
83 89
delete = AuthenticatorDeleteView.as_view()
84 90

  
src/authentic2/authenticators.py
16 16

  
17 17
import logging
18 18

  
19
from django.db.models import Count
20
from django.shortcuts import render
21
from django.utils.translation import ugettext as _
22
from django.utils.translation import ugettext_lazy
23

  
24
from authentic2.a2_rbac.models import OrganizationalUnit as OU
25
from authentic2.a2_rbac.models import Role
26
from authentic2.custom_user.models import User
27

  
28
from . import app_settings, views
29
from .forms import authentication as authentication_forms
30
from .utils import misc as utils_misc
31 19
from .utils.evaluate import evaluate_condition
32
from .utils.service import get_service
33
from .utils.views import csrf_token_check
34 20

  
35 21
logger = logging.getLogger(__name__)
36 22

  
......
61 47

  
62 48
    def get_identifier(self):
63 49
        return self.id
64

  
65

  
66
class LoginPasswordAuthenticator(BaseAuthenticator):
67
    id = 'password'
68
    how = ['password', 'password-on-https']
69
    submit_name = 'login-password-submit'
70
    priority = 0
71

  
72
    def enabled(self):
73
        return app_settings.A2_AUTH_PASSWORD_ENABLE
74

  
75
    def name(self):
76
        return ugettext_lazy('Password')
77

  
78
    def get_service_ous(self, service):
79
        roles = Role.objects.filter(allowed_services=service).children()
80
        if not roles:
81
            return []
82
        service_ou_ids = []
83
        qs = (
84
            User.objects.filter(roles__in=roles)
85
            .values_list('ou')
86
            .annotate(count=Count('ou'))
87
            .order_by('-count')
88
        )
89
        for ou_id, dummy_count in qs:
90
            if not ou_id:
91
                continue
92
            service_ou_ids.append(ou_id)
93
        if not service_ou_ids:
94
            return []
95
        return OU.objects.filter(pk__in=service_ou_ids)
96

  
97
    def get_preferred_ous(self, request):
98
        service = get_service(request)
99
        preferred_ous_cookie = utils_misc.get_remember_cookie(request, 'preferred-ous')
100
        preferred_ous = []
101
        if preferred_ous_cookie:
102
            preferred_ous.extend(OU.objects.filter(pk__in=preferred_ous_cookie))
103
        # for the special case of services open to only one OU, pre-select it
104
        if service:
105
            for ou in self.get_service_ous(service):
106
                if ou in preferred_ous:
107
                    continue
108
                preferred_ous.append(ou)
109
        return preferred_ous
110

  
111
    def login(self, request, *args, **kwargs):
112
        context = kwargs.get('context', {})
113
        is_post = request.method == 'POST' and self.submit_name in request.POST
114
        data = request.POST if is_post else None
115
        initial = {}
116
        preferred_ous = []
117
        request.failed_logins = {}
118

  
119
        # Special handling when the form contains an OU selector
120
        if app_settings.A2_LOGIN_FORM_OU_SELECTOR:
121
            preferred_ous = self.get_preferred_ous(request)
122
            if preferred_ous:
123
                initial['ou'] = preferred_ous[0]
124

  
125
        form = authentication_forms.AuthenticationForm(
126
            request=request, data=data, initial=initial, preferred_ous=preferred_ous
127
        )
128
        if request.user.is_authenticated and request.login_token.get('action'):
129
            form.initial['username'] = request.user.username or request.user.email
130
            form.fields['username'].widget.attrs['readonly'] = True
131
            form.fields['password'].widget.attrs['autofocus'] = True
132
        else:
133
            form.fields['username'].widget.attrs['autofocus'] = not (bool(context.get('block_index')))
134
        if app_settings.A2_ACCEPT_EMAIL_AUTHENTICATION:
135
            form.fields['username'].label = _('Username or email')
136
        if app_settings.A2_USERNAME_LABEL:
137
            form.fields['username'].label = app_settings.A2_USERNAME_LABEL
138
        is_secure = request.is_secure
139
        context['submit_name'] = self.submit_name
140
        if is_post:
141
            csrf_token_check(request, form)
142
            if form.is_valid():
143
                if is_secure:
144
                    how = 'password-on-https'
145
                else:
146
                    how = 'password'
147
                if form.cleaned_data.get('remember_me'):
148
                    request.session['remember_me'] = True
149
                    request.session.set_expiry(app_settings.A2_USER_REMEMBER_ME)
150
                response = utils_misc.login(request, form.get_user(), how)
151
                if 'ou' in form.fields:
152
                    utils_misc.prepend_remember_cookie(
153
                        request, response, 'preferred-ous', form.cleaned_data['ou'].pk
154
                    )
155

  
156
                if hasattr(request, 'needs_password_change'):
157
                    del request.needs_password_change
158
                    return utils_misc.redirect(
159
                        request, 'password_change', params={'next': response.url}, resolve=True
160
                    )
161

  
162
                return response
163
            else:
164
                username = form.cleaned_data.get('username', '').strip()
165
                if request.failed_logins:
166
                    for user, failure_data in request.failed_logins.items():
167
                        request.journal.record(
168
                            'user.login.failure',
169
                            user=user,
170
                            reason=failure_data.get('reason', None),
171
                            username=username,
172
                        )
173
                elif username:
174
                    request.journal.record('user.login.failure', username=username)
175
        context['form'] = form
176
        return render(request, 'authentic2/login_password_form.html', context)
177

  
178
    def profile(self, request, *args, **kwargs):
179
        return views.login_password_profile(request, *args, **kwargs)
180

  
181
    def registration(self, request, *args, **kwargs):
182
        context = kwargs.get('context', {})
183
        return render(request, 'authentic2/login_password_registration_form.html', context)
src/authentic2/forms/authentication.py
51 51

  
52 52
    def __init__(self, *args, **kwargs):
53 53
        preferred_ous = kwargs.pop('preferred_ous', [])
54
        self.authenticator = kwargs.pop('authenticator')
54 55

  
55 56
        super().__init__(*args, **kwargs)
56 57

  
......
60 61
            factor=app_settings.A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_FACTOR,
61 62
        )
62 63

  
63
        if not app_settings.A2_USER_REMEMBER_ME:
64
        if not self.authenticator.remember_me:
64 65
            del self.fields['remember_me']
65 66

  
66
        if not app_settings.A2_LOGIN_FORM_OU_SELECTOR:
67
        if not self.authenticator.include_ou_selector:
67 68
            del self.fields['ou']
68 69
        else:
69 70
            if preferred_ous:
......
135 136
    def media(self):
136 137
        media = super().media
137 138
        media = media + Media(js=['authentic2/js/js_seconds_until.js'])
138
        if app_settings.A2_LOGIN_FORM_OU_SELECTOR:
139
        if self.authenticator.include_ou_selector:
139 140
            media = media + Media(js=['authentic2/js/ou_selector.js'])
140 141
        return media
141 142

  
src/authentic2/settings.py
191 191
    'authentic2_auth_saml.authenticators.SAMLAuthenticator',
192 192
    'authentic2_auth_oidc.authenticators.OIDCAuthenticator',
193 193
    'authentic2_auth_fc.authenticators.FcAuthenticator',
194
) + plugins.register_plugins_authenticators(('authentic2.authenticators.LoginPasswordAuthenticator',))
194
)
195 195

  
196 196
###########################
197 197
# RBAC settings
src/authentic2/utils/misc.py
164 164
    '''Return the list of enabled cleaned backends.'''
165 165
    backends = []
166 166
    if setting_name == 'AUTH_FRONTENDS':
167
        from authentic2.apps.authenticators.models import BaseAuthenticator
167
        from authentic2.apps.authenticators.models import BaseAuthenticator, LoginPasswordAuthenticator
168 168

  
169
        backends = list(BaseAuthenticator.authenticators.filter(enabled=True))
169
        backends = list(
170
            BaseAuthenticator.authenticators.filter(enabled=True).exclude(slug='password-authenticator')
171
        )
172
        password_backend, dummy = LoginPasswordAuthenticator.objects.get_or_create(
173
            slug='password-authenticator'
174
        )
175
        if password_backend.enabled:
176
            backends.append(password_backend)
170 177

  
171 178
    for backend_path in getattr(app_settings, setting_name):
172 179
        kwargs = {}
src/authentic2/views.py
28 28
from django.contrib.auth.decorators import login_required
29 29
from django.contrib.auth.views import PasswordChangeView as DjPasswordChangeView
30 30
from django.core.exceptions import FieldDoesNotExist, ValidationError
31
from django.db.models import Count
31 32
from django.db.models.query import Q
32 33
from django.db.transaction import atomic
33 34
from django.forms import CharField
......
51 52
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
52 53
from ratelimit.utils import is_ratelimited
53 54

  
55
from authentic2.a2_rbac.models import Role
54 56
from authentic2.custom_user.models import iter_attributes
57
from authentic2.forms import authentication as authentication_forms
55 58
from authentic2_idp_oidc.models import OIDCAuthorization
56 59

  
57 60
from . import app_settings, attribute_kinds, cbv, constants, decorators, hooks, models, validators
......
67 70
from .utils.evaluate import make_condition_context
68 71
from .utils.service import get_service, set_home_url
69 72
from .utils.view_decorators import enable_view_restriction
73
from .utils.views import csrf_token_check
70 74

  
71 75
User = get_user_model()
72 76

  
......
697 701
    return response
698 702

  
699 703

  
704
def login_password_login(request, authenticator, *args, **kwargs):
705
    def get_service_ous(service):
706
        roles = Role.objects.filter(allowed_services=service).children()
707
        if not roles:
708
            return []
709
        service_ou_ids = []
710
        qs = (
711
            User.objects.filter(roles__in=roles)
712
            .values_list('ou')
713
            .annotate(count=Count('ou'))
714
            .order_by('-count')
715
        )
716
        for ou_id, dummy_count in qs:
717
            if not ou_id:
718
                continue
719
            service_ou_ids.append(ou_id)
720
        if not service_ou_ids:
721
            return []
722
        return OU.objects.filter(pk__in=service_ou_ids)
723

  
724
    def get_preferred_ous(request):
725
        service = get_service(request)
726
        preferred_ous_cookie = utils_misc.get_remember_cookie(request, 'preferred-ous')
727
        preferred_ous = []
728
        if preferred_ous_cookie:
729
            preferred_ous.extend(OU.objects.filter(pk__in=preferred_ous_cookie))
730
        # for the special case of services open to only one OU, pre-select it
731
        if service:
732
            for ou in get_service_ous(service):
733
                if ou in preferred_ous:
734
                    continue
735
                preferred_ous.append(ou)
736
        return preferred_ous
737

  
738
    context = kwargs.get('context', {})
739
    is_post = request.method == 'POST' and 'login-password-submit' in request.POST
740
    data = request.POST if is_post else None
741
    initial = {}
742
    preferred_ous = []
743
    request.failed_logins = {}
744

  
745
    # Special handling when the form contains an OU selector
746
    if authenticator.include_ou_selector:
747
        preferred_ous = get_preferred_ous(request)
748
        if preferred_ous:
749
            initial['ou'] = preferred_ous[0]
750

  
751
    form = authentication_forms.AuthenticationForm(
752
        request=request, data=data, initial=initial, preferred_ous=preferred_ous, authenticator=authenticator
753
    )
754
    if request.user.is_authenticated and request.login_token.get('action'):
755
        form.initial['username'] = request.user.username or request.user.email
756
        form.fields['username'].widget.attrs['readonly'] = True
757
        form.fields['password'].widget.attrs['autofocus'] = True
758
    else:
759
        form.fields['username'].widget.attrs['autofocus'] = not (bool(context.get('block_index')))
760
    if app_settings.A2_ACCEPT_EMAIL_AUTHENTICATION:
761
        form.fields['username'].label = _('Username or email')
762
    if app_settings.A2_USERNAME_LABEL:
763
        form.fields['username'].label = app_settings.A2_USERNAME_LABEL
764
    is_secure = request.is_secure
765
    context['submit_name'] = 'login-password-submit'
766
    if is_post:
767
        csrf_token_check(request, form)
768
        if form.is_valid():
769
            if is_secure:
770
                how = 'password-on-https'
771
            else:
772
                how = 'password'
773
            if form.cleaned_data.get('remember_me'):
774
                request.session['remember_me'] = True
775
                request.session.set_expiry(authenticator.remember_me)
776
            response = utils_misc.login(request, form.get_user(), how)
777
            if 'ou' in form.fields:
778
                utils_misc.prepend_remember_cookie(
779
                    request, response, 'preferred-ous', form.cleaned_data['ou'].pk
780
                )
781

  
782
            if hasattr(request, 'needs_password_change'):
783
                del request.needs_password_change
784
                return utils_misc.redirect(
785
                    request, 'password_change', params={'next': response.url}, resolve=True
786
                )
787

  
788
            return response
789
        else:
790
            username = form.cleaned_data.get('username', '').strip()
791
            if request.failed_logins:
792
                for user, failure_data in request.failed_logins.items():
793
                    request.journal.record(
794
                        'user.login.failure',
795
                        user=user,
796
                        reason=failure_data.get('reason', None),
797
                        username=username,
798
                    )
799
            elif username:
800
                request.journal.record('user.login.failure', username=username)
801
    context['form'] = form
802
    return render(request, 'authentic2/login_password_form.html', context)
803

  
804

  
700 805
def login_password_profile(request, *args, **kwargs):
701 806
    context = kwargs.pop('context', {})
702 807
    can_change_password = utils_misc.user_can_change_password(request=request)
tests/auth_fc/test_auth_fc.py
30 30

  
31 31
from authentic2.a2_rbac.models import OrganizationalUnit as OU
32 32
from authentic2.a2_rbac.utils import get_default_ou
33
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator
33 34
from authentic2.apps.journal.models import Event
34 35
from authentic2.custom_user.models import DeletedUser
35 36
from authentic2.models import Attribute
......
76 77

  
77 78
def test_login_autorun(settings, app, franceconnect):
78 79
    # hide password block
79
    settings.AUTH_FRONTENDS_KWARGS = {'password': {'show_condition': 'remote_addr==\'0.0.0.0\''}}
80
    LoginPasswordAuthenticator.objects.update_or_create(
81
        slug='password-authenticator', defaults={'enabled': False}
82
    )
80 83
    response = app.get('/login/')
81 84
    assert response.location.startswith('https://fcp')
82 85

  
tests/test_auth_oidc.py
38 38

  
39 39
from authentic2.a2_rbac.models import OrganizationalUnit
40 40
from authentic2.a2_rbac.utils import get_default_ou
41
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator
41 42
from authentic2.custom_user.models import DeletedUser
42 43
from authentic2.models import Attribute, AttributeValue
43 44
from authentic2.utils.misc import last_authentication_event
......
494 495
    assert 'Server' in response
495 496

  
496 497
    # hide password block
497
    settings.AUTH_FRONTENDS_KWARGS = {'password': {'show_condition': 'remote_addr==\'0.0.0.0\''}}
498
    LoginPasswordAuthenticator.objects.update_or_create(
499
        slug='password-authenticator', defaults={'enabled': False}
500
    )
498 501
    response = app.get('/login/', status=302)
499 502
    assert response['Location'] == '/accounts/oidc/login/%s/' % oidc_provider.pk
500 503

  
tests/test_auth_saml.py
24 24
from mellon.adapters import UserCreationError
25 25
from mellon.models import Issuer, UserSAMLIdentifier
26 26

  
27
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator
27 28
from authentic2.custom_user.models import DeletedUser
28 29
from authentic2.models import Attribute
29 30
from authentic2_auth_saml.adapters import AuthenticAdapter, MappingError
......
278 279
        {"METADATA": os.path.join(os.path.dirname(__file__), 'metadata.xml')}
279 280
    ]
280 281
    # hide password block
281
    settings.AUTH_FRONTENDS_KWARGS = {'password': {'show_condition': 'remote_addr==\'0.0.0.0\''}}
282
    LoginPasswordAuthenticator.objects.update_or_create(
283
        slug='password-authenticator', defaults={'enabled': False}
284
    )
282 285
    response = app.get('/login/', status=302)
283 286
    assert '/accounts/saml/login/?entityID=' in response['Location']
284 287

  
tests/test_ldap.py
36 36
from authentic2 import models
37 37
from authentic2.a2_rbac.models import OrganizationalUnit, Role
38 38
from authentic2.a2_rbac.utils import get_default_ou
39
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator
39 40
from authentic2.backends import ldap_backend
40 41
from authentic2.models import Service
41 42
from authentic2.utils import crypto, switch_user
......
1776 1777
            'use_tls': False,
1777 1778
        }
1778 1779
    ]
1779
    settings.A2_LOGIN_FORM_OU_SELECTOR = True
1780
    LoginPasswordAuthenticator.objects.update(include_ou_selector=True)
1780 1781

  
1781 1782
    # Check login to the wrong ou does not work
1782 1783
    response = app.get('/login/')
......
1806 1807
            'use_tls': False,
1807 1808
        }
1808 1809
    ]
1809
    settings.A2_LOGIN_FORM_OU_SELECTOR = True
1810
    LoginPasswordAuthenticator.objects.update(include_ou_selector=True)
1810 1811

  
1811 1812
    # Check login to the wrong ou does not work
1812 1813
    response = app.get('/login/')
tests/test_login.py
20 20
from django.contrib.auth import get_user_model
21 21

  
22 22
from authentic2 import models
23
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator
23 24
from authentic2.utils.misc import get_token_login_url
24 25

  
25 26
from .utils import assert_event, login, set_service
......
72 73
    response = app.get('/login/')
73 74
    assert 'name="login-password-submit"' in response
74 75

  
75
    settings.AUTH_FRONTENDS_KWARGS = {'password': {'show_condition': 'False'}}
76
    LoginPasswordAuthenticator.objects.update(show_condition='False')
76 77
    response = app.get('/login/')
77 78
    # login form must not be displayed
78 79
    assert 'name="login-password-submit"' not in response
79 80
    assert len(caplog.records) == 0
80 81
    # set a condition with error
81 82

  
82
    settings.AUTH_FRONTENDS_KWARGS = {'password': {'show_condition': '\'admin\' in unknown'}}
83
    LoginPasswordAuthenticator.objects.update(show_condition='\'admin\' in unknown')
83 84
    response = app.get('/login/')
84 85
    assert 'name="login-password-submit"' in response
85 86
    assert len(caplog.records) == 1
......
88 89
def test_show_condition_service(db, rf, app, settings):
89 90
    portal = models.Service.objects.create(pk=1, name='Service', slug='portal')
90 91
    service = models.Service.objects.create(pk=2, name='Service', slug='service')
91
    settings.AUTH_FRONTENDS_KWARGS = {'password': {'show_condition': 'service_slug == \'portal\''}}
92
    LoginPasswordAuthenticator.objects.update(show_condition='service_slug == \'portal\'')
92 93

  
93 94
    response = app.get('/login/')
94 95
    assert 'name="login-password-submit"' not in response
......
104 105
    assert 'name="login-password-submit"' not in response
105 106

  
106 107

  
107
def test_show_condition_with_headers(app, settings):
108
def test_show_condition_with_headers(db, app, settings):
108 109
    settings.A2_AUTH_OIDC_ENABLE = False  # prevent db access by OIDC frontend
109
    settings.AUTH_FRONTENDS_KWARGS = {'password': {'show_condition': '\'X-Entrouvert\' in headers'}}
110
    LoginPasswordAuthenticator.objects.update(show_condition='\'X-Entrouvert\' in headers')
110 111
    response = app.get('/login/')
111 112
    assert 'name="login-password-submit"' not in response
112 113
    response = app.get('/login/', headers={'x-entrouvert': '1'})
......
172 173

  
173 174

  
174 175
def test_session_remember_me_ok(app, settings, simple_user, freezer):
175
    settings.A2_USER_REMEMBER_ME = 3600 * 24 * 30
176
    LoginPasswordAuthenticator.objects.update(remember_me=3600 * 24 * 30)
176 177
    freezer.move_to('2018-01-01')
177 178
    # Verify session are longer
178 179
    login(app, simple_user, remember_me=True)
......
187 188

  
188 189

  
189 190
def test_session_remember_me_nok(app, settings, simple_user, freezer):
190
    settings.A2_USER_REMEMBER_ME = 3600 * 24 * 30
191
    LoginPasswordAuthenticator.objects.update(remember_me=3600 * 24 * 30)
191 192
    freezer.move_to('2018-01-01')
192 193
    # Verify session are longer
193 194
    login(app, simple_user, remember_me=True)
......
202 203

  
203 204

  
204 205
def test_ou_selector(app, settings, simple_user, ou1, ou2, user_ou1, role_ou1):
205
    settings.A2_LOGIN_FORM_OU_SELECTOR = True
206
    LoginPasswordAuthenticator.objects.update(include_ou_selector=True)
206 207
    response = app.get('/login/')
207 208
    # Check selector is here and there are no errors
208 209
    assert not response.pyquery('.errorlist')
tests/test_manager_authenticators.py
27 27

  
28 28
    resp = resp.click('Authenticators')
29 29
    assert 'Authenticators' in resp.text
30

  
31

  
32
def test_authenticators_password(app, superuser):
33
    resp = login(app, superuser, path='/manage/authenticators/')
34
    # Password authenticator already exists
35
    assert 'Password' in resp.text
36

  
37
    resp = resp.click('Configure')
38
    assert 'Click "Edit" to change configuration.' in resp.text
39
    # cannot delete password authenticator
40
    assert 'Delete' not in resp.text
41
    app.get('/manage/authenticators/1/delete/', status=403)
42

  
43
    resp = resp.click('Edit')
44
    assert list(resp.form.fields) == [
45
        'csrfmiddlewaretoken',
46
        'order',
47
        'show_condition',
48
        'remember_me',
49
        'include_ou_selector',
50
        None,
51
    ]
52

  
53
    resp.form['show_condition'] = '}'
54
    resp = resp.form.submit()
55
    assert 'template syntax error: Could not parse' in resp.text
56

  
57
    resp.form['show_condition'] = "'backoffice' in login_hint or remotre_addr == '1.2.3.4'"
58
    resp = resp.form.submit().follow()
59
    assert 'Click "Edit" to change configuration.' not in resp.text
60
    assert (
61
        "Show condition: &#39;backoffice&#39; in login_hint or remotre_addr == &#39;1.2.3.4&#39;" in resp.text
62
    )
63

  
64
    resp = resp.click('Disable').follow()
65
    assert 'Authenticator has been disabled.' in resp.text
66

  
67
    resp = app.get('/manage/authenticators/')
68
    assert 'class="section disabled"' in resp.text
69

  
70
    resp = resp.click('Configure')
71
    resp = resp.click('Enable').follow()
72
    assert 'Authenticator has been enabled.' in resp.text
73

  
74
    # cannot add another password authenticator
75
    resp = app.get('/manage/authenticators/add/')
76
    assert 'Password' not in resp.text
30
-