authenticators: add view for login failure events (#76781) #79
|
@ -50,6 +50,11 @@ urlpatterns = [
|
|||
views.journal,
|
||||
name='a2-manager-authenticator-journal',
|
||||
),
|
||||
path(
|
||||
'authenticators/<int:pk>/login-journal/',
|
||||
views.login_journal,
|
||||
name='a2-manager-authenticator-login-journal',
|
||||
),
|
||||
path('authenticators/<int:pk>/export/', views.export_json, name='a2-manager-authenticator-export'),
|
||||
path('authenticators/import/', views.import_json, name='a2-manager-authenticator-import'),
|
||||
path(
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
<a href="{% url 'a2-manager-authenticator-edit' pk=object.pk %}">{% trans "Edit" %}</a>
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a href="{% url 'a2-manager-authenticator-export' pk=object.pk %}">{% trans "Export" %}</a></li>
|
||||
<li><a href="{% url 'a2-manager-authenticator-journal' pk=object.pk %}">{% trans "Journal" %}</a></li>
|
||||
<li><a href="{% url 'a2-manager-authenticator-journal' pk=object.pk %}">{% trans "Journal of edits" %}</a></li>
|
||||
<li><a href="{% url 'a2-manager-authenticator-login-journal' pk=object.pk %}">{% trans "Journal of logins" %}</a></li>
|
||||
{% if not object.protected %}
|
||||
<li><a rel="popup" href="{% url 'a2-manager-authenticator-delete' pk=object.pk %}">{% trans "Delete" %}</a></li>
|
||||
{% endif %}
|
||||
|
|
|
@ -179,22 +179,44 @@ toggle = AuthenticatorToggleView.as_view()
|
|||
|
||||
class AuthenticatorJournal(JournalViewWithContext, BaseJournalView):
|
||||
template_name = 'authentic2/authenticators/authenticator_journal.html'
|
||||
title = _('Journal')
|
||||
title = _('Journal of edits')
|
||||
|
||||
@cached_property
|
||||
def context(self):
|
||||
return get_object_or_404(BaseAuthenticator.authenticators.all(), pk=self.kwargs['pk'])
|
||||
|
||||
def get_events(self):
|
||||
return super().get_events().filter(type__name__startswith='authenticator')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['object'] = self.context
|
||||
ctx['object_name'] = str(self.context)
|
||||
return ctx
|
||||
|
||||
|
||||
journal = AuthenticatorJournal.as_view()
|
||||
|
||||
|
||||
class AuthenticatorLoginJournal(JournalViewWithContext, BaseJournalView):
|
||||
template_name = 'authentic2/authenticators/authenticator_journal.html'
|
||||
title = _('Journal of logins')
|
||||
|
||||
@cached_property
|
||||
def context(self):
|
||||
return get_object_or_404(BaseAuthenticator.authenticators.all(), pk=self.kwargs['pk'])
|
||||
|
||||
def get_events(self):
|
||||
return super().get_events().filter(type__name__startswith='user')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['object'] = self.context
|
||||
return ctx
|
||||
|
||||
|
||||
login_journal = AuthenticatorLoginJournal.as_view()
|
||||
|
||||
|
||||
class AuthenticatorExportView(AuthenticatorsMixin, DetailView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
authenticator = self.get_object()
|
||||
|
|
|
@ -169,19 +169,23 @@ class UserLoginFailure(EventTypeWithService):
|
|||
'username': username,
|
||||
'reason': reason,
|
||||
},
|
||||
references=[authenticator.baseauthenticator_ptr],
|
||||
references=[authenticator],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_message(cls, event, context):
|
||||
username = event.get_data('username')
|
||||
reason = event.get_data('reason')
|
||||
(authenticator,) = event.get_typed_references(BaseAuthenticator)
|
||||
|
||||
(service, authenticator) = event.get_typed_references(Service, BaseAuthenticator)
|
||||
if service is None:
|
||||
(authenticator,) = event.get_typed_references(BaseAuthenticator)
|
||||
|
||||
if username:
|
||||
msg = _('login failure with username "{username}"').format(username=username)
|
||||
else:
|
||||
msg = _('unknown failed login attempt')
|
||||
if authenticator:
|
||||
if authenticator and context != authenticator:
|
||||
msg += _(' on authenticator {authenticator}').format(authenticator=authenticator)
|
||||
if reason:
|
||||
msg.append(_(' (reason: {reason})').format(reason=reason))
|
||||
|
|
|
@ -126,7 +126,7 @@ def test_authenticators_password(app, superuser_or_admin, settings):
|
|||
assert 'Disable' not in resp.text
|
||||
app.get('/manage/authenticators/%s/toggle/' % authenticator.pk, status=403)
|
||||
|
||||
resp = resp.click('Journal')
|
||||
resp = resp.click('Journal of edits')
|
||||
assert resp.text.count('edit (show_condition)') == 2
|
||||
|
||||
# cannot add another password authenticator
|
||||
|
@ -272,7 +272,7 @@ def test_authenticators_oidc(app, superuser, ou1, ou2):
|
|||
assert 'Authenticator has been enabled.' in resp.text
|
||||
assert_event('authenticator.enable', user=superuser, session=app.session)
|
||||
|
||||
resp = resp.click('Journal')
|
||||
resp = resp.click('Journal of edits')
|
||||
assert 'enable' in resp.text
|
||||
assert (
|
||||
'edit (ou, issuer, scopes, strategy, client_id, button_label, idtoken_algo, '
|
||||
|
@ -600,7 +600,7 @@ def test_authenticators_saml(app, superuser, ou1, ou2):
|
|||
assert resp.pyquery('button#tab-advanced').attr('class') == 'pk-tabs--button-marker'
|
||||
|
||||
resp = app.get(authenticator.get_absolute_url())
|
||||
resp = resp.click('Journal')
|
||||
resp = resp.click('Journal of edits')
|
||||
assert 'edit (metadata_url)' in resp.text
|
||||
|
||||
|
||||
|
@ -881,3 +881,27 @@ def test_authenticators_configuration_info(app, superuser, ou1, ou2):
|
|||
'Redirect URI after logout (post_logout_redirect_uri):<br><a href="https://testserver/logout/" '
|
||||
'rel="nofollow">https://testserver/logout/</a>'
|
||||
) in resp.text
|
||||
|
||||
|
||||
def test_authenticators_journal_pages(app, superuser):
|
||||
authenticator = LoginPasswordAuthenticator.objects.get()
|
||||
|
||||
# generate login failure event
|
||||
login(app, 'noone', password='wrong', fail=True)
|
||||
|
||||
login(app, superuser)
|
||||
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk)
|
||||
|
||||
# generate edit event
|
||||
resp.form['button_description'] = 'abc'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
resp = app.get('/manage/authenticators/%s/detail/' % authenticator.pk)
|
||||
resp = resp.click('Journal of edits')
|
||||
assert resp.pyquery('td.journal-list--message-column').text() == 'edit (button_description)'
|
||||
assert 'noone' not in resp.text
|
||||
|
||||
resp = app.get('/manage/authenticators/%s/detail/' % authenticator.pk)
|
||||
resp = resp.click('Journal of logins')
|
||||
assert resp.pyquery('td.journal-list--message-column').text() == 'login failure with username "noone"'
|
||||
assert 'edit (button_description)' not in resp.text
|
||||
|
|
|
@ -84,7 +84,7 @@ def events(db, superuser, freezer):
|
|||
)
|
||||
make("user.logout", user=user, session=session1)
|
||||
|
||||
make("user.login.failure", authenticator=saml_authenticator, username="user")
|
||||
make("user.login.failure", service=service, authenticator=saml_authenticator, username="user")
|
||||
make("user.login.failure", authenticator=saml_authenticator, username="agent")
|
||||
make("user.login", user=user, session=session1, how="password")
|
||||
make("user.password.change", user=user, session=session1)
|
||||
|
@ -397,13 +397,13 @@ def test_global_journal(app, superuser, events):
|
|||
'user': 'Johnny doe',
|
||||
},
|
||||
{
|
||||
'message': 'login failure with username "user" on authenticator base authenticator - saml',
|
||||
'message': 'login failure with username "user" on authenticator SAML - saml',
|
||||
'timestamp': 'Jan. 1, 2020, 3 a.m.',
|
||||
'type': 'user.login.failure',
|
||||
'user': '-',
|
||||
},
|
||||
{
|
||||
'message': 'login failure with username "agent" on authenticator base authenticator - saml',
|
||||
'message': 'login failure with username "agent" on authenticator SAML - saml',
|
||||
'timestamp': 'Jan. 1, 2020, 4 a.m.',
|
||||
'type': 'user.login.failure',
|
||||
'user': '-',
|
||||
|
@ -1230,7 +1230,7 @@ def test_search(app, superuser, events):
|
|||
for p in zip(pq('tbody td.journal-list--user-column'), pq('tbody td.journal-list--message-column'))
|
||||
] == [
|
||||
['agent', 'login using SAML'],
|
||||
['-', 'login failure with username "agent" on authenticator base authenticator - saml'],
|
||||
['-', 'login failure with username "agent" on authenticator SAML - saml'],
|
||||
]
|
||||
|
||||
response.form.set('search', 'uuid:%s event:reset' % events['user'].uuid)
|
||||
|
|
Loading…
Reference in New Issue