Projet

Général

Profil

0002-misc-add-support-for-request-based-enable-conditions.patch

Serghei Mihai (congés, retour 15/05), 01 février 2019 16:43

Télécharger (11,7 ko)

Voir les différences:

Subject: [PATCH 2/2] misc: add support for request based enable conditions on
 authentication frontends (#28215)

 src/authentic2/app_settings.py                |  1 +
 .../auth2_auth/auth2_ssl/app_settings.py      |  1 +
 .../auth2_auth/auth2_ssl/frontends.py         |  9 ++++---
 src/authentic2/auth_frontends.py              | 25 ++++++++++++++++---
 src/authentic2/registration_backend/views.py  |  2 +-
 src/authentic2/utils.py                       |  4 +--
 src/authentic2/views.py                       |  8 +++---
 src/authentic2_auth_oidc/app_settings.py      |  4 +++
 src/authentic2_auth_oidc/auth_frontends.py    | 11 +++++---
 src/authentic2_auth_saml/app_settings.py      |  1 -
 src/authentic2_auth_saml/auth_frontends.py    |  9 ++++---
 tests/test_login.py                           | 20 ++++++++++++++-
 12 files changed, 74 insertions(+), 21 deletions(-)
src/authentic2/app_settings.py
157 157
        definition='path of a class to validate passwords'),
158 158
    A2_PASSWORD_POLICY_SHOW_LAST_CHAR=Setting(default=False, definition='Show last character in password fields'),
159 159
    A2_AUTH_PASSWORD_ENABLE=Setting(default=True, definition='Activate login/password authentication', names=('AUTH_PASSWORD',)),
160
    A2_AUTH_PASSWORD_ENABLE_CONDITION=Setting(default=None, definition='Filters', names=('FRONTEND ENABLE CONDITION',)),
160 161
    A2_LOGIN_FAILURE_COUNT_BEFORE_WARNING=Setting(default=0,
161 162
            definition='Failure count before logging a warning to '
162 163
            'authentic2.user_login_failure. No warning will be send if value is '
src/authentic2/auth2_auth/auth2_ssl/app_settings.py
16 16
            CREATE_USERNAME_CALLBACK=None,
17 17
            USE_COOKIE=False,
18 18
            CREATE_USER=False,
19
            ENABLE_CONDITION=None
19 20
    )
20 21

  
21 22
    def __init__(self, prefix):
src/authentic2/auth2_auth/auth2_ssl/frontends.py
3 3

  
4 4
from . import views, app_settings
5 5
from authentic2.utils import redirect_to_login
6
from authentic2.auth_frontends import BaseFrontend
6 7

  
7 8

  
8
class SSLFrontend(object):
9
    def enabled(self):
10
        return app_settings.ENABLE
9
class SSLFrontend(BaseFrontend):
10
    def enabled(self, request=None):
11
        if app_settings.ENABLE:
12
            return self.eval_enable_condition(app_settings.ENABLE_CONDITION, request)
13
        return False
11 14

  
12 15
    def id(self):
13 16
        return 'ssl'
src/authentic2/auth_frontends.py
1
import logging
2

  
1 3
from django.shortcuts import render
2 4
from django.utils.translation import ugettext as _, ugettext_lazy
3 5

  
4 6
from . import views, app_settings, utils, constants, forms
5 7

  
6 8

  
7
class LoginPasswordBackend(object):
9
class BaseFrontend(object):
10
    def eval_enable_condition(self, condition, request, **kwargs):
11
        if condition:
12
            try:
13
                context = {'request': request}
14
                if kwargs:
15
                    context.update(kwargs)
16
                return eval(condition, context)
17
            except Exception, e:
18
                logging.getLogger(__name__).warning('filter expression error: %r' % e)
19
                return False
20
        return True
21

  
22

  
23
class LoginPasswordBackend(BaseFrontend):
24

  
8 25
    submit_name = 'login-password-submit'
9 26

  
10
    def enabled(self):
11
        return app_settings.A2_AUTH_PASSWORD_ENABLE
27
    def enabled(self, request=None):
28
        if app_settings.A2_AUTH_PASSWORD_ENABLE:
29
            return self.eval_enable_condition(app_settings.A2_AUTH_PASSWORD_ENABLE_CONDITION, request)
30
        return False
12 31

  
13 32
    def name(self):
14 33
        return ugettext_lazy('Password')
src/authentic2/registration_backend/views.py
95 95
        parameters = {'request': self.request,
96 96
                      'context': context}
97 97
        blocks = [utils.get_backend_method(backend, 'registration', parameters)
98
                  for backend in utils.get_backends('AUTH_FRONTENDS')]
98
                  for backend in utils.get_backends('AUTH_FRONTENDS', self.request)]
99 99
        context['frontends'] = collections.OrderedDict((block['id'], block)
100 100
                                                       for block in blocks if block)
101 101
        return context
src/authentic2/utils.py
152 152
    return cls()
153 153

  
154 154

  
155
def get_backends(setting_name='IDP_BACKENDS'):
155
def get_backends(setting_name='IDP_BACKENDS', request=None):
156 156
    '''Return the list of enabled cleaned backends.'''
157 157
    backends = []
158 158
    for backend_path in getattr(app_settings, setting_name):
......
161 161
            backend_path, kwargs = backend_path
162 162
        backend = load_backend(backend_path)
163 163
        # If no enabled method is defined on the backend, backend enabled by default.
164
        if hasattr(backend, 'enabled') and not backend.enabled():
164
        if hasattr(backend, 'enabled') and not backend.enabled(request):
165 165
            continue
166 166
        kwargs_settings = getattr(app_settings, setting_name + '_KWARGS', {})
167 167
        if backend_path in kwargs_settings:
src/authentic2/views.py
281 281
            redirect_to = settings.LOGIN_REDIRECT_URL
282 282
    nonce = request.GET.get(constants.NONCE_FIELD_NAME)
283 283

  
284
    frontends = utils.get_backends('AUTH_FRONTENDS')
284
    frontends = utils.get_backends('AUTH_FRONTENDS', request)
285 285

  
286 286
    blocks = []
287 287

  
......
403 403
        return super(ProfileView, self).dispatch(request, *args, **kwargs)
404 404

  
405 405
    def get_context_data(self, **kwargs):
406
        context = super(ProfileView, self).get_context_data(**kwargs)
407
        frontends = utils.get_backends('AUTH_FRONTENDS')
408

  
409 406
        request = self.request
410 407

  
408
        context = super(ProfileView, self).get_context_data(**kwargs)
409
        frontends = utils.get_backends('AUTH_FRONTENDS', request)
410

  
411 411
        if request.method == "POST":
412 412
            for frontend in frontends:
413 413
                if 'submit-%s' % frontend.id in request.POST:
src/authentic2_auth_oidc/app_settings.py
18 18
    def ENABLE(self):
19 19
        return self._setting('ENABLE', True)
20 20

  
21
    @property
22
    def IDP_ENABLE_CONDITION(self):
23
        return self._setting('IDP_ENABLE_CONDITION', None)
24

  
21 25

  
22 26
import sys
23 27

  
src/authentic2_auth_oidc/auth_frontends.py
2 2
from django.shortcuts import render
3 3

  
4 4
from . import app_settings, utils
5
from authentic2.auth_frontends import BaseFrontend
5 6

  
6 7

  
7
class OIDCFrontend(object):
8
    def enabled(self):
8
class OIDCFrontend(BaseFrontend):
9
    def enabled(self, request=None):
9 10
        return app_settings.ENABLE and utils.has_providers()
10 11

  
11 12
    def name(self):
......
16 17

  
17 18
    def login(self, request, *args, **kwargs):
18 19
        context = kwargs.get('context', {})
19
        context['providers'] = utils.get_providers(shown=True)
20
        providers = utils.get_providers(shown=True)
21
        if app_settings.IDP_ENABLE_CONDITION is not None:
22
            providers = self.eval_enable_condition(app_settings.IDP_ENABLE_CONDITION, request,
23
                                                   providers=providers)
24
        context['providers'] = providers
20 25
        return render(request, 'authentic2_auth_oidc/login.html', context)
src/authentic2_auth_saml/app_settings.py
19 19
    def enable(self):
20 20
        return self._setting('ENABLE', False)
21 21

  
22

  
23 22
import sys
24 23

  
25 24
app_settings = AppSettings('A2_AUTH_SAML_')
src/authentic2_auth_saml/auth_frontends.py
4 4
from mellon.utils import get_idp, get_idps
5 5

  
6 6
from authentic2.utils import redirect_to_login
7
from authentic2.auth_frontends import BaseFrontend
7 8

  
8 9
from . import app_settings
9 10

  
10 11

  
11
class SAMLFrontend(object):
12
class SAMLFrontend(BaseFrontend):
12 13
    id = 'saml'
13 14

  
14
    def enabled(self):
15
        return app_settings.enable and list(get_idps())
15
    def enabled(self, request=None):
16
        if app_settings.enable:
17
            return self.eval_enable_condition(app_settings.enable_condition, request)
18
        return False
16 19

  
17 20
    def name(self):
18 21
        return gettext_noop('SAML')
tests/test_login.py
3 3

  
4 4
from django.contrib.auth import get_user_model
5 5

  
6
from utils import login
6
from utils import login, check_log
7 7

  
8 8

  
9 9
def test_login_inactive_user(db, app):
......
31 31
        login(app, user1)
32 32
    assert '_auth_user_id' not in app.session
33 33

  
34
def test_login_with_conditionnal_enabled_frontend(db, app, settings, caplog):
35
    settings.A2_AUTH_PASSWORD_ENABLE_CONDITION = "request.META['REMOTE_ADDR'] in ('127.0.0.1',)"
36
    response = app.get('/login/')
37
    assert 'name="login-password-submit"' in response
38

  
39
    # set invalid display condition
40
    settings.A2_AUTH_PASSWORD_ENABLE_CONDITION = "request.META['REMOTE_ADDR'] in ('0.0.0.0',)"
41
    response = app.get('/login/')
42
    # login form must not be displayed
43
    assert 'name="login-password-submit"' not in response
44
    assert len(caplog.records) == 0
45

  
46
    # set a condition with error
47
    with check_log(caplog, 'filter expression error: SyntaxError(\'unexpected EOF while parsing\''):
48
        settings.A2_AUTH_PASSWORD_ENABLE_CONDITION = "request.META['REMOTE_ADDR'] in "
49
        response = app.get('/login/')
50
        assert 'name="login-password-submit"' not in response
51

  
34 52

  
35 53
def test_registration_url_on_login_page(db, app):
36 54
    response = app.get('/login/?next=/whatever')
37
-