Compare commits

...

1 Commits

Author SHA1 Message Date
Roberto Rosario
bee0c0b189 Add authentication events
Add event to track failed logins, password reset starts, and password
reset completions.

Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-06-27 17:04:44 -04:00
5 changed files with 144 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _
from mayan.apps.events.classes import EventTypeNamespace
namespace = EventTypeNamespace(
label=_('Authentication'), name='authentication'
)
event_user_authentication_error = namespace.add_event_type(
label=_('User authentication error'), name='user_authentication_error'
)
event_user_password_reset_started = namespace.add_event_type(
label=_('User password reset started'), name='user_password_reset_started'
)
event_user_password_reset_complete = namespace.add_event_type(
label=_('User password reset complete'), name='user_password_reset_complete'
)

View File

@@ -0,0 +1,82 @@
from __future__ import unicode_literals
from django.conf import settings
from django.contrib.auth.views import (
INTERNAL_RESET_SESSION_TOKEN, INTERNAL_RESET_URL_TOKEN,
)
from django.core import mail
from actstream.models import Action
from mayan.apps.common.tests import GenericViewTestCase
from mayan.apps.events.utils import create_system_user
from ..events import (
event_user_authentication_error, event_user_password_reset_complete,
event_user_password_reset_started
)
class AuthenticationEventsTestCase(GenericViewTestCase):
auto_login_user = False
def setUp(self):
super(AuthenticationEventsTestCase, self).setUp()
create_system_user()
def test_user_authentication_failure_event(self):
Action.objects.all().delete()
response = self.post(viewname=settings.LOGIN_URL)
self.assertEqual(response.status_code, 200)
action = Action.objects.last()
self.assertEqual(action.verb, event_user_authentication_error.id)
def test_user_password_reset_started_event(self):
Action.objects.all().delete()
response = self.post(
viewname='authentication:password_reset_view', data={
'email': self._test_case_user.email,
}
)
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
action = Action.objects.last()
self.assertEqual(action.verb, event_user_password_reset_started.id)
def test_user_password_reset_complete_event(self):
response = self.post(
viewname='authentication:password_reset_view', data={
'email': self._test_case_user.email,
}
)
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
email_parts = mail.outbox[0].body.replace('\n', '').split('/')
uidb64 = email_parts[-3]
token = email_parts[-2]
# Add the token to the session
session = self.client.session
session[INTERNAL_RESET_SESSION_TOKEN] = token
session.save()
Action.objects.all().delete()
new_password = 'new_password_123'
response = self.post(
viewname='authentication:password_reset_confirm_view',
kwargs={'uidb64': uidb64, 'token': INTERNAL_RESET_URL_TOKEN}, data={
'new_password1': new_password,
'new_password2': new_password
}
)
self.assertNotIn(INTERNAL_RESET_SESSION_TOKEN, self.client.session)
action = Action.objects.last()
self.assertEqual(action.verb, event_user_password_reset_complete.id)

View File

@@ -21,8 +21,13 @@ from mayan.apps.common.generics import MultipleObjectFormActionView
from mayan.apps.common.settings import (
setting_home_view, setting_project_title, setting_project_url
)
from mayan.apps.events.utils import get_system_user
from mayan.apps.user_management.permissions import permission_user_edit
from .events import (
event_user_authentication_error, event_user_password_reset_complete,
event_user_password_reset_started
)
from .forms import EmailAuthenticationForm, UsernameAuthenticationForm
from .settings import setting_login_method, setting_maximum_session_length
@@ -57,6 +62,10 @@ class MayanLoginView(StrongholdPublicMixin, LoginView):
return result
def form_invalid(self, form):
event_user_authentication_error.commit(actor=get_system_user())
return super(MayanLoginView, self).form_invalid(form=form)
def get_form_class(self):
if setting_login_method.value == 'email':
return EmailAuthenticationForm
@@ -112,6 +121,10 @@ class MayanPasswordResetConfirmView(StrongholdPublicMixin, PasswordResetConfirmV
)
template_name = 'authentication/password_reset_confirm.html'
def post(self, *args, **kwargs):
event_user_password_reset_complete.commit(actor=get_system_user())
return super(MayanPasswordResetConfirmView, self).post(*args, **kwargs)
class MayanPasswordResetDoneView(StrongholdPublicMixin, PasswordResetDoneView):
extra_context = {
@@ -137,6 +150,10 @@ class MayanPasswordResetView(StrongholdPublicMixin, PasswordResetView):
)
template_name = 'authentication/password_reset_form.html'
def post(self, *args, **kwargs):
event_user_password_reset_started.commit(actor=get_system_user())
return super(MayanPasswordResetView, self).post(*args, **kwargs)
class UserSetPasswordView(MultipleObjectFormActionView):
form_class = SetPasswordForm

View File

@@ -19,6 +19,7 @@ from .links import (
link_events_list, link_notification_mark_read,
link_notification_mark_read_all, link_user_notifications_list,
)
from .utils import create_system_user
class EventsApp(MayanAppConfig):
@@ -101,3 +102,5 @@ class EventsApp(MayanAppConfig):
link_event_types_subscriptions_list, link_current_user_events
), position=50
)
create_system_user()

View File

@@ -0,0 +1,23 @@
from __future__ import absolute_import, unicode_literals
from django.contrib.auth import get_user_model
def create_system_user():
"""
User account without a password used to attach events that normally
won't have an actor and a target
"""
user, created = get_user_model().objects.get_or_create(
username='system', defaults={
'first_name': 'System', 'is_staff': False
}
)
return user
def get_system_user():
user = get_user_model().objects.get(username='system')
return user