0001-misc-add-support-for-enable-conditions-on-authentica.patch
src/authentic2/app_settings.py | ||
---|---|---|
239 | 239 |
A2_AUTH_PASSWORD_ENABLE=Setting( |
240 | 240 |
default=True, |
241 | 241 |
definition='Activate login/password authentication', names=('AUTH_PASSWORD',)), |
242 |
A2_AUTH_PASSWORD_ENABLE_CONDITION=Setting(default=None, definition='Filters', |
|
243 |
names=('FRONTEND ENABLE CONDITION',)), |
|
242 | 244 |
A2_LOGIN_FAILURE_COUNT_BEFORE_WARNING=Setting( |
243 | 245 |
default=0, |
244 | 246 |
definition='Failure count before logging a warning to ' |
src/authentic2/authenticators.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import logging |
|
18 | ||
17 | 19 |
from django.shortcuts import render |
18 | 20 |
from django.utils.translation import ugettext as _, ugettext_lazy |
19 | 21 | |
20 | 22 |
from authentic2.a2_rbac.models import OrganizationalUnit as OU, Role |
21 | 23 |
from authentic2.custom_user.models import User |
24 | ||
22 | 25 |
from . import views, app_settings, utils, constants, models |
23 | 26 |
from .forms import authentication as authentication_forms |
24 | 27 | |
25 | 28 | |
26 |
class LoginPasswordAuthenticator(object): |
|
29 |
class BaseAuthenticator(object): |
|
30 |
def eval_enable_condition(self, condition, request, **kwargs): |
|
31 |
if condition: |
|
32 |
try: |
|
33 |
context = {'request': request} |
|
34 |
if kwargs: |
|
35 |
context.update(kwargs) |
|
36 |
return eval(condition, context) |
|
37 |
except Exception, e: |
|
38 |
logging.getLogger(__name__).warning('filter expression error: %r' % e) |
|
39 |
return False |
|
40 |
return True |
|
41 | ||
42 | ||
43 |
class LoginPasswordAuthenticator(BaseAuthenticator): |
|
27 | 44 |
submit_name = 'login-password-submit' |
28 | 45 | |
29 |
def enabled(self): |
|
30 |
return app_settings.A2_AUTH_PASSWORD_ENABLE |
|
46 |
def enabled(self, request=None): |
|
47 |
if app_settings.A2_AUTH_PASSWORD_ENABLE: |
|
48 |
return self.eval_enable_condition(app_settings.A2_AUTH_PASSWORD_ENABLE_CONDITION, request) |
|
49 |
return False |
|
31 | 50 | |
32 | 51 |
def name(self): |
33 | 52 |
return ugettext_lazy('Password') |
src/authentic2/utils/__init__.py | ||
---|---|---|
169 | 169 |
return cls() |
170 | 170 | |
171 | 171 | |
172 |
def get_backends(setting_name='IDP_BACKENDS'): |
|
172 |
def get_backends(setting_name='IDP_BACKENDS', request=None):
|
|
173 | 173 |
'''Return the list of enabled cleaned backends.''' |
174 | 174 |
backends = [] |
175 | 175 |
for backend_path in getattr(app_settings, setting_name): |
... | ... | |
178 | 178 |
backend_path, kwargs = backend_path |
179 | 179 |
backend = load_backend(backend_path) |
180 | 180 |
# If no enabled method is defined on the backend, backend enabled by default. |
181 |
if hasattr(backend, 'enabled') and not backend.enabled(): |
|
181 |
if hasattr(backend, 'enabled') and not backend.enabled(request):
|
|
182 | 182 |
continue |
183 | 183 |
kwargs_settings = getattr(app_settings, setting_name + '_KWARGS', {}) |
184 | 184 |
if backend_path in kwargs_settings: |
src/authentic2/views.py | ||
---|---|---|
277 | 277 |
redirect_to = settings.LOGIN_REDIRECT_URL |
278 | 278 |
nonce = request.GET.get(constants.NONCE_FIELD_NAME) |
279 | 279 | |
280 |
authenticators = utils.get_backends('AUTH_FRONTENDS') |
|
280 |
authenticators = utils.get_backends('AUTH_FRONTENDS', request)
|
|
281 | 281 | |
282 | 282 |
blocks = [] |
283 | 283 |
src/authentic2_auth_fc/app_settings.py | ||
---|---|---|
134 | 134 |
def popup(self): |
135 | 135 |
return self._setting('POPUP', False) |
136 | 136 | |
137 |
@property |
|
138 |
def enable_condition(self): |
|
139 |
return self._setting('ENABLE_CONDITION', None) |
|
140 | ||
137 | 141 |
app_settings = AppSettings('A2_FC_') |
138 | 142 |
app_settings.__name__ = __name__ |
139 | 143 |
sys.modules[__name__] = app_settings |
src/authentic2_auth_fc/authenticators.py | ||
---|---|---|
19 | 19 |
from django.template.response import TemplateResponse |
20 | 20 | |
21 | 21 |
from authentic2 import app_settings as a2_app_settings, utils as a2_utils |
22 |
from authentic2.authenticators import BaseAuthenticator |
|
22 | 23 | |
23 | 24 |
from . import app_settings |
24 | 25 | |
25 | 26 | |
26 |
class FcAuthenticator(object): |
|
27 |
def enabled(self): |
|
27 |
class FcAuthenticator(BaseAuthenticator): |
|
28 |
def enabled(self, request): |
|
29 |
if app_settings.enable: |
|
30 |
return self.eval_enable_condition(app_settings.enable_condition, request) |
|
28 | 31 |
return app_settings.enable |
29 | 32 | |
30 | 33 |
def name(self): |
src/authentic2_auth_oidc/app_settings.py | ||
---|---|---|
37 | 37 |
def ENABLE(self): |
38 | 38 |
return self._setting('ENABLE', True) |
39 | 39 | |
40 |
@property |
|
41 |
def IDP_ENABLE_CONDITION(self): |
|
42 |
return self._setting('IDP_ENABLE_CONDITION', None) |
|
43 | ||
40 | 44 |
app_settings = AppSettings('A2_AUTH_OIDC_') |
41 | 45 |
app_settings.__name__ = __name__ |
42 | 46 |
sys.modules[__name__] = app_settings |
src/authentic2_auth_oidc/authenticators.py | ||
---|---|---|
19 | 19 | |
20 | 20 |
from . import app_settings, utils |
21 | 21 |
from authentic2.utils import make_url |
22 |
from authentic2.authenticators import BaseAuthenticator |
|
22 | 23 | |
23 | 24 | |
24 |
class OIDCAuthenticator(object):
|
|
25 |
def enabled(self): |
|
25 |
class OIDCAuthenticator(BaseAuthenticator):
|
|
26 |
def enabled(self, request=None):
|
|
26 | 27 |
return app_settings.ENABLE and utils.has_providers() |
27 | 28 | |
28 | 29 |
def name(self): |
... | ... | |
32 | 33 |
return 'oidc' |
33 | 34 | |
34 | 35 |
def instances(self, request, *args, **kwargs): |
35 |
for p in utils.get_providers(shown=True): |
|
36 |
providers = utils.get_providers(shown=True) |
|
37 |
if app_settings.IDP_ENABLE_CONDITION: |
|
38 |
providers = self.eval_enable_condition(app_settings.IDP_ENABLE_CONDITION, request, |
|
39 |
providers=providers) |
|
40 |
for p in providers: |
|
36 | 41 |
yield (p.slug, p) |
37 | 42 | |
38 | 43 |
def login(self, request, *args, **kwargs): |
src/authentic2_auth_saml/app_settings.py | ||
---|---|---|
37 | 37 |
def enable(self): |
38 | 38 |
return self._setting('ENABLE', False) |
39 | 39 | |
40 |
@property |
|
41 |
def enable_condition(self): |
|
42 |
return self._setting('ENABLE_CONDITION', False) |
|
43 | ||
40 | 44 |
app_settings = AppSettings('A2_AUTH_SAML_') |
41 | 45 |
app_settings.__name__ = __name__ |
42 | 46 |
sys.modules[__name__] = app_settings |
src/authentic2_auth_saml/authenticators.py | ||
---|---|---|
20 | 20 |
from mellon.utils import get_idp, get_idps |
21 | 21 | |
22 | 22 |
from authentic2.utils import redirect_to_login |
23 |
from authentic2.authenticators import BaseAuthenticator |
|
24 | ||
23 | 25 | |
24 | 26 |
from . import app_settings |
25 | 27 | |
26 | 28 | |
27 |
class SAMLAuthenticator(object):
|
|
29 |
class SAMLAuthenticator(BaseAuthenticator):
|
|
28 | 30 |
id = 'saml' |
29 | 31 | |
30 |
def enabled(self): |
|
31 |
return app_settings.enable and list(get_idps()) |
|
32 |
def enabled(self, request=None): |
|
33 |
idps = list(get_idps()) |
|
34 |
if app_settings.enable and idps: |
|
35 |
return self.eval_enable_condition(app_settings.enable_condition, request, idps=idps) |
|
36 |
return False |
|
32 | 37 | |
33 | 38 |
def name(self): |
34 | 39 |
return gettext_noop('SAML') |
tests/test_auth_oidc.py | ||
---|---|---|
331 | 331 |
assert response.pyquery('p#oidc-p-oidcidp-2') |
332 | 332 | |
333 | 333 | |
334 |
def test_login_with_conditionnal_authenticators(oidc_provider, app, settings, caplog): |
|
335 | ||
336 |
OIDCProvider.objects.create( |
|
337 |
id=2, |
|
338 |
ou=get_default_ou(), |
|
339 |
name='My IDP', |
|
340 |
issuer='https://idp2.example.com/', |
|
341 |
authorization_endpoint='https://idp2.example.com/authorize', |
|
342 |
token_endpoint='https://idp2.example.com/token', |
|
343 |
end_session_endpoint='https://idp2.example.com/logout', |
|
344 |
userinfo_endpoint='https://idp*é.example.com/user_info', |
|
345 |
token_revocation_endpoint='https://idp2.example.com/revoke', |
|
346 |
max_auth_age=10, |
|
347 |
strategy=OIDCProvider.STRATEGY_CREATE, |
|
348 |
jwkset_json=None, |
|
349 |
idtoken_algo=OIDCProvider.ALGO_RSA, |
|
350 |
claims_parameter_supported=False |
|
351 |
) |
|
352 |
settings.A2_AUTH_OIDC_IDP_ENABLE_CONDITION = "providers.filter(name='My IDP')" |
|
353 |
response = app.get('/login/') |
|
354 |
assert 'My IDP' in response |
|
355 |
assert 'OIDIDP' not in response |
|
356 |
assert len(caplog.records) == 0 |
|
357 | ||
358 |
settings.A2_AUTH_OIDC_IDP_ENABLE_CONDITION = "providers.filter(name='OIDIDP')" |
|
359 |
response = app.get('/login/') |
|
360 |
assert 'OIDIDP' in response |
|
361 |
assert 'My IDP' not in response |
|
362 | ||
363 | ||
334 | 364 | |
335 | 365 | |
336 | 366 |
def test_sso(app, caplog, code, oidc_provider, oidc_provider_jwkset, login_url, login_callback_url, hooks): |
tests/test_login.py | ||
---|---|---|
21 | 21 | |
22 | 22 |
from authentic2 import models |
23 | 23 | |
24 |
from utils import login |
|
24 |
from utils import login, check_log
|
|
25 | 25 | |
26 | 26 | |
27 | 27 |
def test_login_inactive_user(db, app): |
... | ... | |
50 | 50 |
assert '_auth_user_id' not in app.session |
51 | 51 | |
52 | 52 | |
53 |
def test_login_with_conditionnal_enabled_authenticators(db, app, settings, caplog): |
|
54 |
settings.A2_AUTH_PASSWORD_ENABLE_CONDITION = "request.META['REMOTE_ADDR'] in ('127.0.0.1',)" |
|
55 |
response = app.get('/login/') |
|
56 |
assert 'name="login-password-submit"' in response |
|
57 |
# set invalid display condition |
|
58 |
settings.A2_AUTH_PASSWORD_ENABLE_CONDITION = "request.META['REMOTE_ADDR'] in ('0.0.0.0',)" |
|
59 |
response = app.get('/login/') |
|
60 |
# login form must not be displayed |
|
61 |
assert 'name="login-password-submit"' not in response |
|
62 |
assert len(caplog.records) == 0 |
|
63 |
# set a condition with error |
|
64 |
with check_log(caplog, 'filter expression error: SyntaxError(\'unexpected EOF while parsing\''): |
|
65 |
settings.A2_AUTH_PASSWORD_ENABLE_CONDITION = "request.META['REMOTE_ADDR'] in " |
|
66 |
response = app.get('/login/') |
|
67 |
assert 'name="login-password-submit"' not in response |
|
68 | ||
69 | ||
53 | 70 |
def test_registration_url_on_login_page(db, app): |
54 | 71 |
response = app.get('/login/?next=/whatever') |
55 | 72 |
assert 'register/?next=/whatever"' in response |
56 |
- |