Projet

Général

Profil

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

Serghei Mihai, 22 novembre 2018 15:50

Télécharger (10,5 ko)

Voir les différences:

Subject: [PATCH] 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/authenticators.py    |  9 ++++++---
 src/authentic2/authenticators.py              | 20 ++++++++++++++++---
 src/authentic2/utils.py                       |  4 ++--
 src/authentic2/views.py                       |  8 ++++----
 src/authentic2_auth_oidc/app_settings.py      |  4 ++++
 src/authentic2_auth_oidc/authenticators.py    |  9 ++++++---
 src/authentic2_auth_saml/app_settings.py      |  4 ++++
 src/authentic2_auth_saml/authenticators.py    |  9 ++++++---
 tests/test_login.py                           | 20 ++++++++++++++++++-
 11 files changed, 70 insertions(+), 19 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/authenticators.py
3 3

  
4 4
from . import views, app_settings
5 5
from authentic2.utils import redirect_to_login
6
from authentic2.authenticators import BaseAuthenticator
6 7

  
7 8

  
8
class SSLAuthenticator(object):
9
    def enabled(self):
10
        return app_settings.ENABLE
9
class SSLAuthenticator(BaseAuthenticator):
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/authenticators.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

  
8
class BaseAuthenticator(object):
9
    def eval_enable_condition(self, condition, request):
10
        if condition:
11
            try:
12
                return eval(condition, {'request': request})
13
            except Exception, e:
14
                logging.getLogger(__name__).warning('filter expression error: %r' % e)
15
                return False
16
        return True
17

  
6 18

  
7
class LoginPasswordAuthenticator(object):
19
class LoginPasswordAuthenticator(BaseAuthenticator):
8 20
    submit_name = 'login-password-submit'
9 21

  
10
    def enabled(self):
11
        return app_settings.A2_AUTH_PASSWORD_ENABLE
22
    def enabled(self, request=None):
23
        if app_settings.A2_AUTH_PASSWORD_ENABLE:
24
            return self.eval_enable_condition(app_settings.A2_AUTH_PASSWORD_ENABLE_CONDITION, request)
25
        return False
12 26

  
13 27
    def name(self):
14 28
        return ugettext_lazy('Password')
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

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

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

  
408 405
        request = self.request
409 406

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

  
410 410
        if request.method == "POST":
411 411
            for frontend in frontends:
412 412
                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 ENABLE_CONDITION(self):
23
        return self._setting('ENDABLE_CONDITION', None)
24

  
21 25

  
22 26
import sys
23 27

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

  
4 4
from . import app_settings, utils
5
from authentic2.authenticators import BaseAuthenticator
5 6

  
6 7

  
7
class OIDCAuthenticator(object):
8
    def enabled(self):
9
        return app_settings.ENABLE and utils.has_providers()
8
class OIDCAuthenticator(BaseAuthenticator):
9
    def enabled(self, request=None):
10
        if app_settings.ENABLE and utils.has_providers():
11
            return self.eval_enable_condition(app_settings.ENABLE_CONDITION, request)
12
        return False
10 13

  
11 14
    def name(self):
12 15
        return gettext_noop('OpenIDConnect')
src/authentic2_auth_saml/app_settings.py
19 19
    def enable(self):
20 20
        return self._setting('ENABLE', False)
21 21

  
22
    @property
23
    def enable_condition(self):
24
        return self._setting('ENABLE_CONDITION', None)
25

  
22 26

  
23 27
import sys
24 28

  
src/authentic2_auth_saml/authenticators.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.authenticators import BaseAuthenticator
7 8

  
8 9
from . import app_settings
9 10

  
10 11

  
11
class SAMLAuthenticator(object):
12
class SAMLAuthenticator(BaseAuthenticator):
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
-