0001-misc-add-support-for-request-based-enable-conditions.patch
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 |
- |