api: record actions in journal (#48010)
This commit is contained in:
parent
b7b9a3babb
commit
78b07aa497
|
@ -343,6 +343,7 @@ class PasswordChange(BaseRpcView):
|
||||||
def rpc(self, request, serializer):
|
def rpc(self, request, serializer):
|
||||||
serializer.user.set_password(serializer.validated_data['new_password'])
|
serializer.user.set_password(serializer.validated_data['new_password'])
|
||||||
serializer.user.save()
|
serializer.user.save()
|
||||||
|
request.journal.record('manager.user.password.change', form=serializer, api=True)
|
||||||
return {'result': 1}, status.HTTP_200_OK
|
return {'result': 1}, status.HTTP_200_OK
|
||||||
|
|
||||||
|
|
||||||
|
@ -778,8 +779,19 @@ class UsersAPI(api_mixins.GetOrCreateMixinView, HookMixin, ExceptionHandlerMixin
|
||||||
if not self.request.user.has_perm(perm):
|
if not self.request.user.has_perm(perm):
|
||||||
raise PermissionDenied(u'You do not have permission %s' % perm)
|
raise PermissionDenied(u'You do not have permission %s' % perm)
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
super().perform_create(serializer)
|
||||||
|
self.request.journal.record('manager.user.creation', form=serializer, api=True)
|
||||||
|
|
||||||
|
def perform_update(self, serializer):
|
||||||
|
super().perform_update(serializer)
|
||||||
|
attributes = serializer.validated_data.pop('attributes', {})
|
||||||
|
serializer.validated_data.update(attributes)
|
||||||
|
self.request.journal.record('manager.user.profile.edit', form=serializer, api=True)
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
self.check_perm('custom_user.delete_user', instance.ou)
|
self.check_perm('custom_user.delete_user', instance.ou)
|
||||||
|
self.request.journal.record('manager.user.deletion', target_user=instance, api=True)
|
||||||
super(UsersAPI, self).perform_destroy(instance)
|
super(UsersAPI, self).perform_destroy(instance)
|
||||||
|
|
||||||
class SynchronizationSerializer(serializers.Serializer):
|
class SynchronizationSerializer(serializers.Serializer):
|
||||||
|
@ -820,6 +832,7 @@ class UsersAPI(api_mixins.GetOrCreateMixinView, HookMixin, ExceptionHandlerMixin
|
||||||
)
|
)
|
||||||
|
|
||||||
utils.send_password_reset_mail(user, request=request)
|
utils.send_password_reset_mail(user, request=request)
|
||||||
|
request.journal.record('manager.user.password.reset.request', target_user=user, api=True)
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
@action(detail=True, methods=['post'], permission_classes=(DjangoPermission('custom_user.change_user'),))
|
@action(detail=True, methods=['post'], permission_classes=(DjangoPermission('custom_user.change_user'),))
|
||||||
|
@ -875,8 +888,17 @@ class RolesAPI(api_mixins.GetOrCreateMixinView, ExceptionHandlerMixin, ModelView
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
if not self.request.user.has_perm(perm='a2_rbac.delete_role', obj=instance):
|
if not self.request.user.has_perm(perm='a2_rbac.delete_role', obj=instance):
|
||||||
raise PermissionDenied(u'User %s can\'t create role %s' % (self.request.user, instance))
|
raise PermissionDenied(u'User %s can\'t create role %s' % (self.request.user, instance))
|
||||||
|
self.request.journal.record('manager.role.deletion', role=instance, api=True)
|
||||||
super(RolesAPI, self).perform_destroy(instance)
|
super(RolesAPI, self).perform_destroy(instance)
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
super().perform_create(serializer)
|
||||||
|
self.request.journal.record('manager.role.creation', role=serializer.instance, api=True)
|
||||||
|
|
||||||
|
def perform_update(self, serializer):
|
||||||
|
super().perform_update(serializer)
|
||||||
|
self.request.journal.record('manager.role.edit', role=serializer.instance, form=serializer, api=True)
|
||||||
|
|
||||||
|
|
||||||
class RolesMembersAPI(UsersAPI):
|
class RolesMembersAPI(UsersAPI):
|
||||||
def initial(self, request, *args, **kwargs):
|
def initial(self, request, *args, **kwargs):
|
||||||
|
@ -919,6 +941,7 @@ class RoleMembershipAPI(ExceptionHandlerMixin, APIView):
|
||||||
if not request.user.has_perm('a2_rbac.manage_members_role', obj=self.role):
|
if not request.user.has_perm('a2_rbac.manage_members_role', obj=self.role):
|
||||||
raise PermissionDenied(u'User not allowed to manage role members')
|
raise PermissionDenied(u'User not allowed to manage role members')
|
||||||
self.role.members.add(self.member)
|
self.role.members.add(self.member)
|
||||||
|
request.journal.record('manager.role.membership.grant', role=self.role, member=self.member, api=True)
|
||||||
return Response(
|
return Response(
|
||||||
{'result': 1, 'detail': _('User successfully added to role')}, status=status.HTTP_201_CREATED
|
{'result': 1, 'detail': _('User successfully added to role')}, status=status.HTTP_201_CREATED
|
||||||
)
|
)
|
||||||
|
@ -927,6 +950,9 @@ class RoleMembershipAPI(ExceptionHandlerMixin, APIView):
|
||||||
if not request.user.has_perm('a2_rbac.manage_members_role', obj=self.role):
|
if not request.user.has_perm('a2_rbac.manage_members_role', obj=self.role):
|
||||||
raise PermissionDenied(u'User not allowed to manage role members')
|
raise PermissionDenied(u'User not allowed to manage role members')
|
||||||
self.role.members.remove(self.member)
|
self.role.members.remove(self.member)
|
||||||
|
request.journal.record(
|
||||||
|
'manager.role.membership.removal', role=self.role, member=self.member, api=True
|
||||||
|
)
|
||||||
return Response(
|
return Response(
|
||||||
{'result': 1, 'detail': _('User successfully removed from role')}, status=status.HTTP_200_OK
|
{'result': 1, 'detail': _('User successfully removed from role')}, status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
|
@ -944,7 +970,7 @@ class RoleMembershipsAPI(ExceptionHandlerMixin, APIView):
|
||||||
Role = get_role_model()
|
Role = get_role_model()
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
self.role = get_object_or_404(Role, uuid=kwargs['role_uuid'])
|
self.role = get_object_or_404(Role, uuid=kwargs['role_uuid'])
|
||||||
self.members = []
|
self.members = set()
|
||||||
|
|
||||||
perm = 'a2_rbac.manage_members_role'
|
perm = 'a2_rbac.manage_members_role'
|
||||||
authorized = request.user.has_perm(perm, obj=self.role)
|
authorized = request.user.has_perm(perm, obj=self.role)
|
||||||
|
@ -967,7 +993,7 @@ class RoleMembershipsAPI(ExceptionHandlerMixin, APIView):
|
||||||
_("Missing 'uuid' key for dict entry %s " "of the 'data' payload") % entry
|
_("Missing 'uuid' key for dict entry %s " "of the 'data' payload") % entry
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
self.members.append(User.objects.get(uuid=uuid))
|
self.members.add(User.objects.get(uuid=uuid))
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
raise ValidationError(_('No known user for UUID %s') % entry['uuid'])
|
raise ValidationError(_('No known user for UUID %s') % entry['uuid'])
|
||||||
|
|
||||||
|
@ -976,18 +1002,27 @@ class RoleMembershipsAPI(ExceptionHandlerMixin, APIView):
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.role.members.add(*self.members)
|
self.role.members.add(*self.members)
|
||||||
|
for member in self.members:
|
||||||
|
request.journal.record('manager.role.membership.grant', role=self.role, member=member, api=True)
|
||||||
return Response(
|
return Response(
|
||||||
{'result': 1, 'detail': _('Users successfully added to role')}, status=status.HTTP_201_CREATED
|
{'result': 1, 'detail': _('Users successfully added to role')}, status=status.HTTP_201_CREATED
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
self.role.members.remove(*self.members)
|
self.role.members.remove(*self.members)
|
||||||
|
for member in self.members:
|
||||||
|
request.journal.record('manager.role.membership.removal', role=self.role, member=member, api=True)
|
||||||
return Response(
|
return Response(
|
||||||
{'result': 1, 'detail': _('Users successfully removed from role')}, status=status.HTTP_200_OK
|
{'result': 1, 'detail': _('Users successfully removed from role')}, status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
|
|
||||||
def patch(self, request, *args, **kwargs):
|
def patch(self, request, *args, **kwargs):
|
||||||
|
old_members = set(self.role.members.all())
|
||||||
self.role.members.set(self.members)
|
self.role.members.set(self.members)
|
||||||
|
for member in self.members:
|
||||||
|
request.journal.record('manager.role.membership.grant', role=self.role, member=member, api=True)
|
||||||
|
for member in old_members.difference(self.members):
|
||||||
|
request.journal.record('manager.role.membership.removal', role=self.role, member=member, api=True)
|
||||||
return Response(
|
return Response(
|
||||||
{'result': 1, 'detail': _('Users successfully assigned to role')}, status=status.HTTP_200_OK
|
{'result': 1, 'detail': _('Users successfully assigned to role')}, status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.19 on 2021-05-19 15:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('journal', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='api',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='API'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -86,15 +86,20 @@ class EventTypeDefinition(metaclass=EventTypeDefinitionMeta):
|
||||||
retention_days = None
|
retention_days = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(cls, user=None, session=None, references=None, data=None):
|
def record(cls, user=None, session=None, references=None, data=None, api=False):
|
||||||
event_type = EventType.objects.get_for_name(cls.name)
|
event_type = EventType.objects.get_for_name(cls.name)
|
||||||
|
|
||||||
|
if user and not isinstance(user, User):
|
||||||
|
# API user from DRF or OIDC
|
||||||
|
user = None
|
||||||
|
|
||||||
Event.objects.create(
|
Event.objects.create(
|
||||||
type=event_type,
|
type=event_type,
|
||||||
user=user,
|
user=user,
|
||||||
session_id=session and session.session_key,
|
session_id=session and session.session_key,
|
||||||
references=references or None, # NULL values take less space
|
references=references or None, # NULL values take less space
|
||||||
data=data or None, # NULL values take less space
|
data=data or None, # NULL values take less space
|
||||||
|
api=api,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -321,6 +326,8 @@ class Event(models.Model):
|
||||||
|
|
||||||
data = JSONField(verbose_name=_('data'), null=True)
|
data = JSONField(verbose_name=_('data'), null=True)
|
||||||
|
|
||||||
|
api = models.BooleanField(verbose_name=_('API'), default=False)
|
||||||
|
|
||||||
objects = EventManager.from_queryset(EventQuerySet)()
|
objects = EventManager.from_queryset(EventQuerySet)()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
@ -136,3 +136,12 @@ to users with this email address.</tt>.'''
|
||||||
You can use <tt>username:john</tt> to find all events related \
|
You can use <tt>username:john</tt> to find all events related \
|
||||||
to users whose username is <tt>john</tt>.'''
|
to users whose username is <tt>john</tt>.'''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def search_by_api(self, lexem):
|
||||||
|
yield Q(api=bool(lexem == 'true'))
|
||||||
|
|
||||||
|
search_by_api.documentation = _(
|
||||||
|
'''\
|
||||||
|
You can use <tt>api:true</tt> to find all events related \
|
||||||
|
to API calls.'''
|
||||||
|
)
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<tr data-event-id="{{ event.id }}" data-event-cursor="{{ event.cursor }}" data-event-type="{{ event.type.name }}">
|
<tr data-event-id="{{ event.id }}" data-event-cursor="{{ event.cursor }}" data-event-type="{{ event.type.name }}">
|
||||||
<td class="journal-list--timestamp-column">{% block event-timestamp %}{{ event.timestamp }}{% endblock %}</td>
|
<td class="journal-list--timestamp-column">{% block event-timestamp %}{{ event.timestamp }}{% endblock %}</td>
|
||||||
<td class="journal-list--user-column" {% if event.user %}data-user-id="{{ event.user.id }}"{% endif %}>{% block event-user %}{% firstof event.user.get_full_name event.user "-" %}{% endblock %}</td>
|
<td class="journal-list--user-column" {% if event.user %}data-user-id="{{ event.user.id }}"{% endif %}>{% block event-user %}{% firstof event.user.get_full_name event.user "-" %}{% endblock %}</td>
|
||||||
<td class="journal-list--session-column">{% block event-session %}{{ event.session_id_shortened|default:"-" }}{% endblock %}</td>
|
<td class="journal-list--session-column">{% block event-session %}{% if event.api %}API{% else %}{{ event.session_id_shortened|default:"-" }}{% endif %}{% endblock %}</td>
|
||||||
<td class="journal-list--message-column">{% block event-message %}{{ event.message|default:"-" }}{% endblock %}</td>
|
<td class="journal-list--message-column">{% block event-message %}{{ event.message|default:"-" }}{% endblock %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if forloop.last %}
|
{% if forloop.last %}
|
||||||
|
|
|
@ -26,6 +26,9 @@ def _json_value(value):
|
||||||
|
|
||||||
|
|
||||||
def form_to_old_new(form):
|
def form_to_old_new(form):
|
||||||
|
if hasattr(form, 'validated_data'):
|
||||||
|
# form is a DRF serializer
|
||||||
|
return {'new': {k: _json_value(v) for k, v in form.validated_data.items()}}
|
||||||
old = {}
|
old = {}
|
||||||
new = {}
|
new = {}
|
||||||
for key in form.changed_data:
|
for key in form.changed_data:
|
||||||
|
|
|
@ -42,8 +42,8 @@ class ManagerUserCreation(EventTypeDefinition):
|
||||||
label = _('user creation')
|
label = _('user creation')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(cls, user, session, form):
|
def record(cls, user, session, form, api=False):
|
||||||
super().record(user=user, session=session, references=[form.instance])
|
super().record(user=user, session=session, references=[form.instance], api=api)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_message(cls, event, context):
|
def get_message(cls, event, context):
|
||||||
|
@ -62,8 +62,10 @@ class ManagerUserProfileEdit(EventTypeDefinition):
|
||||||
label = _('user profile edit')
|
label = _('user profile edit')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(cls, user, session, form):
|
def record(cls, user, session, form, api=False):
|
||||||
super().record(user=user, session=session, references=[form.instance], data=form_to_old_new(form))
|
super().record(
|
||||||
|
user=user, session=session, references=[form.instance], data=form_to_old_new(form), api=api
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_message(cls, event, context):
|
def get_message(cls, event, context):
|
||||||
|
@ -107,12 +109,13 @@ class ManagerUserPasswordChange(EventTypeDefinition):
|
||||||
label = _('user password change')
|
label = _('user password change')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(cls, user, session, form):
|
def record(cls, user, session, form, api=False):
|
||||||
|
cleaned_data = getattr(form, 'cleaned_data', {})
|
||||||
data = {
|
data = {
|
||||||
'generate_password': form.cleaned_data['generate_password'],
|
'generate_password': cleaned_data.get('generate_password', False),
|
||||||
'send_mail': form.cleaned_data['send_mail'],
|
'send_mail': cleaned_data.get('send_mail', False),
|
||||||
}
|
}
|
||||||
super().record(user=user, session=session, references=[form.instance], data=data)
|
super().record(user=user, session=session, references=[form.instance], data=data, api=api)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_message(cls, event, context):
|
def get_message(cls, event, context):
|
||||||
|
@ -137,9 +140,9 @@ class ManagerUserPasswordResetRequest(EventTypeDefinition):
|
||||||
label = _('user password reset request')
|
label = _('user password reset request')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(cls, user, session, target_user):
|
def record(cls, user, session, target_user, api=False):
|
||||||
super().record(
|
super().record(
|
||||||
user=user, session=session, references=[target_user], data={'email': target_user.email}
|
user=user, session=session, references=[target_user], data={'email': target_user.email}, api=api
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -256,8 +259,8 @@ class ManagerUserDeletion(EventTypeDefinition):
|
||||||
label = _('user deletion')
|
label = _('user deletion')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(cls, user, session, target_user):
|
def record(cls, user, session, target_user, api=False):
|
||||||
super().record(user=user, session=session, references=[target_user])
|
super().record(user=user, session=session, references=[target_user], api=api)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_message(cls, event, context):
|
def get_message(cls, event, context):
|
||||||
|
@ -296,7 +299,7 @@ class ManagerUserSSOAuthorizationDeletion(EventTypeWithService):
|
||||||
|
|
||||||
class RoleEventsMixin(EventTypeDefinition):
|
class RoleEventsMixin(EventTypeDefinition):
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(self, user, session, role, references=None, data=None):
|
def record(self, user, role, session=None, references=None, data=None, api=False):
|
||||||
references = references or []
|
references = references or []
|
||||||
references = [role] + references
|
references = [role] + references
|
||||||
data = data or {}
|
data = data or {}
|
||||||
|
@ -306,6 +309,7 @@ class RoleEventsMixin(EventTypeDefinition):
|
||||||
session=session,
|
session=session,
|
||||||
references=references,
|
references=references,
|
||||||
data=data,
|
data=data,
|
||||||
|
api=api,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -328,8 +332,8 @@ class ManagerRoleEdit(RoleEventsMixin):
|
||||||
label = _('role edit')
|
label = _('role edit')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(cls, user, session, role, form):
|
def record(cls, user, session, role, form, api=False):
|
||||||
super().record(user=user, session=session, role=role, data=form_to_old_new(form))
|
super().record(user=user, session=session, role=role, data=form_to_old_new(form), api=api)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_message(cls, event, context):
|
def get_message(cls, event, context):
|
||||||
|
@ -362,9 +366,9 @@ class ManagerRoleMembershipGrant(RoleEventsMixin):
|
||||||
label = _('role membership grant')
|
label = _('role membership grant')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(cls, user, session, role, member):
|
def record(cls, user, session, role, member, api=False):
|
||||||
data = {'member_name': member.get_full_name()}
|
data = {'member_name': member.get_full_name()}
|
||||||
super().record(user=user, session=session, role=role, references=[member], data=data)
|
super().record(user=user, session=session, role=role, references=[member], data=data, api=api)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_message(cls, event, context):
|
def get_message(cls, event, context):
|
||||||
|
@ -384,9 +388,9 @@ class ManagerRoleMembershipRemoval(RoleEventsMixin):
|
||||||
label = _('role membership removal')
|
label = _('role membership removal')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def record(cls, user, session, role, member):
|
def record(cls, user, session, role, member, api=False):
|
||||||
data = {'member_name': member.get_full_name()}
|
data = {'member_name': member.get_full_name()}
|
||||||
super().record(user=user, session=session, role=role, references=[member], data=data)
|
super().record(user=user, session=session, role=role, references=[member], data=data, api=api)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_message(cls, event, context):
|
def get_message(cls, event, context):
|
||||||
|
|
|
@ -42,7 +42,7 @@ from authentic2.utils import good_next_url
|
||||||
from django_rbac.models import SEARCH_OP
|
from django_rbac.models import SEARCH_OP
|
||||||
from django_rbac.utils import get_ou_model, get_role_model
|
from django_rbac.utils import get_ou_model, get_role_model
|
||||||
|
|
||||||
from .utils import basic_authorization_header, get_link_from_mail, login
|
from .utils import assert_event, basic_authorization_header, get_link_from_mail, login
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
@ -243,6 +243,7 @@ def test_api_users_update_with_email_verified(settings, app, admin, simple_user)
|
||||||
user = User.objects.get(id=simple_user.id)
|
user = User.objects.get(id=simple_user.id)
|
||||||
assert user.email_verified
|
assert user.email_verified
|
||||||
assert resp.json['email_verified']
|
assert resp.json['email_verified']
|
||||||
|
assert_event('manager.user.profile.edit', user=admin, api=True)
|
||||||
|
|
||||||
user.email_verified = True
|
user.email_verified = True
|
||||||
user.email = 'johnny.doeny@foo.bar'
|
user.email = 'johnny.doeny@foo.bar'
|
||||||
|
@ -329,6 +330,7 @@ def test_api_users_create_with_email_verified(settings, app, admin):
|
||||||
assert resp.json['email_verified']
|
assert resp.json['email_verified']
|
||||||
user = User.objects.get(uuid=resp.json['uuid'])
|
user = User.objects.get(uuid=resp.json['uuid'])
|
||||||
assert user.email_verified
|
assert user.email_verified
|
||||||
|
assert_event('manager.user.creation', user=admin, api=True)
|
||||||
|
|
||||||
|
|
||||||
def test_api_users_create_without_email_verified(settings, app, admin):
|
def test_api_users_create_without_email_verified(settings, app, admin):
|
||||||
|
@ -786,6 +788,13 @@ def test_api_role_add_member(app, api_user, role, member):
|
||||||
elif authorized:
|
elif authorized:
|
||||||
assert resp.json['result'] == 1
|
assert resp.json['result'] == 1
|
||||||
assert resp.json['detail'] == 'User successfully added to role'
|
assert resp.json['detail'] == 'User successfully added to role'
|
||||||
|
assert_event(
|
||||||
|
'manager.role.membership.grant',
|
||||||
|
user=api_user if isinstance(api_user, User) else None,
|
||||||
|
api=True,
|
||||||
|
role_name=role.name,
|
||||||
|
member_name=member.get_full_name(),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert resp.json['result'] == 0
|
assert resp.json['result'] == 0
|
||||||
assert resp.json['errors'] == 'User not allowed to manage role members'
|
assert resp.json['errors'] == 'User not allowed to manage role members'
|
||||||
|
@ -812,6 +821,13 @@ def test_api_role_remove_member(app, api_user, role, member):
|
||||||
assert resp.json['detail'] == 'User successfully removed from role'
|
assert resp.json['detail'] == 'User successfully removed from role'
|
||||||
resp = app.get('/api/roles/{0}/members/{1}/'.format(role.uuid, member.uuid), status=404)
|
resp = app.get('/api/roles/{0}/members/{1}/'.format(role.uuid, member.uuid), status=404)
|
||||||
assert resp.json == {'result': 0, 'errors': {'detail': 'Not found.'}}
|
assert resp.json == {'result': 0, 'errors': {'detail': 'Not found.'}}
|
||||||
|
assert_event(
|
||||||
|
'manager.role.membership.removal',
|
||||||
|
user=api_user if isinstance(api_user, User) else None,
|
||||||
|
api=True,
|
||||||
|
role_name=role.name,
|
||||||
|
member_name=member.get_full_name(),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert resp.json['result'] == 0
|
assert resp.json['result'] == 0
|
||||||
assert resp.json['errors'] == 'User not allowed to manage role members'
|
assert resp.json['errors'] == 'User not allowed to manage role members'
|
||||||
|
@ -847,6 +863,13 @@ def test_api_role_add_members(app, api_user, role, member, member_rando2):
|
||||||
assert resp.json['detail'] == 'Users successfully added to role'
|
assert resp.json['detail'] == 'Users successfully added to role'
|
||||||
for m in [member, member_rando2]:
|
for m in [member, member_rando2]:
|
||||||
assert m in role.members.all()
|
assert m in role.members.all()
|
||||||
|
assert_event(
|
||||||
|
'manager.role.membership.grant',
|
||||||
|
user=api_user if isinstance(api_user, User) else None,
|
||||||
|
api=True,
|
||||||
|
role_name=role.name,
|
||||||
|
member_name=m.get_full_name(),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert resp.json['result'] == 0
|
assert resp.json['result'] == 0
|
||||||
assert resp.json['errors'] == 'User not allowed to manage role members'
|
assert resp.json['errors'] == 'User not allowed to manage role members'
|
||||||
|
@ -882,12 +905,22 @@ def test_api_role_remove_members(app, api_user, role, member, member_rando2):
|
||||||
assert resp.json['detail'] == 'Users successfully removed from role'
|
assert resp.json['detail'] == 'Users successfully removed from role'
|
||||||
for m in [member, member_rando2]:
|
for m in [member, member_rando2]:
|
||||||
assert m not in role.members.all()
|
assert m not in role.members.all()
|
||||||
|
assert_event(
|
||||||
|
'manager.role.membership.removal',
|
||||||
|
user=api_user if isinstance(api_user, User) else None,
|
||||||
|
api=True,
|
||||||
|
role_name=role.name,
|
||||||
|
member_name=m.get_full_name(),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert resp.json['result'] == 0
|
assert resp.json['result'] == 0
|
||||||
assert resp.json['errors'] == 'User not allowed to manage role members'
|
assert resp.json['errors'] == 'User not allowed to manage role members'
|
||||||
|
|
||||||
|
|
||||||
def test_api_role_set_members(app, api_user, role, member, member_rando2):
|
def test_api_role_set_members(app, api_user, role, member, member_rando2, ou_rando):
|
||||||
|
user = User.objects.create(
|
||||||
|
username='test3', first_name='test3', last_name='test3', email='test3@test.org', ou=ou_rando
|
||||||
|
)
|
||||||
app.authorization = ('Basic', (api_user.username, api_user.username))
|
app.authorization = ('Basic', (api_user.username, api_user.username))
|
||||||
|
|
||||||
authorized = api_user.has_perm('a2_rbac.manage_members_role', role)
|
authorized = api_user.has_perm('a2_rbac.manage_members_role', role)
|
||||||
|
@ -903,6 +936,7 @@ def test_api_role_set_members(app, api_user, role, member, member_rando2):
|
||||||
|
|
||||||
payload = {"data": []}
|
payload = {"data": []}
|
||||||
|
|
||||||
|
role.members.add(user)
|
||||||
for m in [member, member_rando2, member_rando2]: # test no duplicate
|
for m in [member, member_rando2, member_rando2]: # test no duplicate
|
||||||
payload['data'].append({"uuid": m.uuid})
|
payload['data'].append({"uuid": m.uuid})
|
||||||
|
|
||||||
|
@ -918,6 +952,20 @@ def test_api_role_set_members(app, api_user, role, member, member_rando2):
|
||||||
assert len(role.members.all()) == 2
|
assert len(role.members.all()) == 2
|
||||||
for m in [member, member_rando2]:
|
for m in [member, member_rando2]:
|
||||||
assert m in role.members.all()
|
assert m in role.members.all()
|
||||||
|
assert_event(
|
||||||
|
'manager.role.membership.grant',
|
||||||
|
user=api_user if isinstance(api_user, User) else None,
|
||||||
|
api=True,
|
||||||
|
role_name=role.name,
|
||||||
|
member_name=m.get_full_name(),
|
||||||
|
)
|
||||||
|
assert_event(
|
||||||
|
'manager.role.membership.removal',
|
||||||
|
user=api_user if isinstance(api_user, User) else None,
|
||||||
|
api=True,
|
||||||
|
role_name=role.name,
|
||||||
|
member_name=user.get_full_name(),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert resp.json['result'] == 0
|
assert resp.json['result'] == 0
|
||||||
assert resp.json['errors'] == 'User not allowed to manage role members'
|
assert resp.json['errors'] == 'User not allowed to manage role members'
|
||||||
|
@ -1226,6 +1274,7 @@ def test_password_change(app, ou1, admin):
|
||||||
response = app.post_json(url, params=payload)
|
response = app.post_json(url, params=payload)
|
||||||
assert response.json['result'] == 1
|
assert response.json['result'] == 1
|
||||||
assert User.objects.get(username='john.doe').check_password('password2')
|
assert User.objects.get(username='john.doe').check_password('password2')
|
||||||
|
assert_event('manager.user.password.change', user=admin, api=True)
|
||||||
|
|
||||||
|
|
||||||
def test_password_reset(app, ou1, admin, user_ou1, mailoutbox):
|
def test_password_reset(app, ou1, admin, user_ou1, mailoutbox):
|
||||||
|
@ -1247,6 +1296,7 @@ def test_password_reset(app, ou1, admin, user_ou1, mailoutbox):
|
||||||
mail = mailoutbox[0]
|
mail = mailoutbox[0]
|
||||||
assert mail.to[0] == email
|
assert mail.to[0] == email
|
||||||
assert 'http://testserver/accounts/password/reset/confirm/' in mail.body
|
assert 'http://testserver/accounts/password/reset/confirm/' in mail.body
|
||||||
|
assert_event('manager.user.password.reset.request', user=admin, api=True)
|
||||||
|
|
||||||
|
|
||||||
def test_users_email(app, ou1, admin, user_ou1, mailoutbox):
|
def test_users_email(app, ou1, admin, user_ou1, mailoutbox):
|
||||||
|
@ -1274,6 +1324,7 @@ def test_api_delete_role(app, admin_ou1, role_ou1):
|
||||||
app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
|
app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
|
||||||
app.delete('/api/roles/{}/'.format(role_ou1.uuid))
|
app.delete('/api/roles/{}/'.format(role_ou1.uuid))
|
||||||
assert not len(Role.objects.filter(slug='role_ou1'))
|
assert not len(Role.objects.filter(slug='role_ou1'))
|
||||||
|
assert_event('manager.role.deletion', user=admin_ou1, api=True, role_name=role_ou1.name)
|
||||||
|
|
||||||
|
|
||||||
def test_api_delete_role_unauthorized(app, simple_user, role_ou1):
|
def test_api_delete_role_unauthorized(app, simple_user, role_ou1):
|
||||||
|
@ -1289,6 +1340,7 @@ def test_api_patch_role(app, admin_ou1, role_ou1):
|
||||||
'slug': 'updated-role',
|
'slug': 'updated-role',
|
||||||
}
|
}
|
||||||
app.patch_json('/api/roles/{}/'.format(role_ou1.uuid), params=role_data)
|
app.patch_json('/api/roles/{}/'.format(role_ou1.uuid), params=role_data)
|
||||||
|
assert_event('manager.role.edit', user=admin_ou1, api=True, role_name=role_ou1.name)
|
||||||
|
|
||||||
# The role API won't change the organizational unit attribute:
|
# The role API won't change the organizational unit attribute:
|
||||||
role_ou1.refresh_from_db()
|
role_ou1.refresh_from_db()
|
||||||
|
@ -1317,6 +1369,7 @@ def test_api_put_role(app, admin_ou1, role_ou1, ou1):
|
||||||
assert role_ou1.name == 'updated-role'
|
assert role_ou1.name == 'updated-role'
|
||||||
assert role_ou1.slug == 'updated-role'
|
assert role_ou1.slug == 'updated-role'
|
||||||
assert role_ou1.ou.slug == 'ou1'
|
assert role_ou1.ou.slug == 'ou1'
|
||||||
|
assert_event('manager.role.edit', user=admin_ou1, api=True, role_name=role_ou1.name)
|
||||||
|
|
||||||
|
|
||||||
def test_api_put_role_unauthorized(app, simple_user, role_ou1, ou1):
|
def test_api_put_role_unauthorized(app, simple_user, role_ou1, ou1):
|
||||||
|
@ -1334,6 +1387,7 @@ def test_api_post_role(app, admin_ou1, ou1):
|
||||||
|
|
||||||
role_data = {'slug': 'coffee-manager', 'name': 'Coffee Manager', 'ou': 'ou1'}
|
role_data = {'slug': 'coffee-manager', 'name': 'Coffee Manager', 'ou': 'ou1'}
|
||||||
resp = app.post_json('/api/roles/', params=role_data)
|
resp = app.post_json('/api/roles/', params=role_data)
|
||||||
|
assert_event('manager.role.creation', user=admin_ou1, api=True, role_name='Coffee Manager')
|
||||||
assert isinstance(resp.json, dict)
|
assert isinstance(resp.json, dict)
|
||||||
uuid = resp.json['uuid']
|
uuid = resp.json['uuid']
|
||||||
|
|
||||||
|
@ -1870,12 +1924,14 @@ def test_api_users_required_date_attributes(settings, app, admin, simple_user):
|
||||||
]
|
]
|
||||||
|
|
||||||
# update with values pass
|
# update with values pass
|
||||||
|
del payload['id']
|
||||||
payload['prefered_color'] = 'blue'
|
payload['prefered_color'] = 'blue'
|
||||||
payload['date'] = '1515-1-15'
|
payload['date'] = '1515-01-15'
|
||||||
payload['birthdate'] = '1900-2-22'
|
payload['birthdate'] = '1900-02-22'
|
||||||
resp = app.put_json(
|
resp = app.put_json(
|
||||||
'/api/users/{}/'.format(simple_user.uuid), params=payload, headers=headers, status=200
|
'/api/users/{}/'.format(simple_user.uuid), params=payload, headers=headers, status=200
|
||||||
)
|
)
|
||||||
|
assert_event('manager.user.profile.edit', user=admin, api=True, new=payload)
|
||||||
|
|
||||||
# value are properly returned on a get
|
# value are properly returned on a get
|
||||||
resp = app.get('/api/users/{}/'.format(simple_user.uuid), headers=headers, status=200)
|
resp = app.get('/api/users/{}/'.format(simple_user.uuid), headers=headers, status=200)
|
||||||
|
@ -2247,6 +2303,13 @@ def test_api_password_change_user_delete(app, settings, admin, ou1):
|
||||||
assert User.objects.get(username='john.doe').check_password('password2')
|
assert User.objects.get(username='john.doe').check_password('password2')
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_users_delete(settings, app, admin, simple_user):
|
||||||
|
headers = basic_authorization_header(admin)
|
||||||
|
resp = app.delete_json('/api/users/{}/'.format(simple_user.uuid), headers=headers)
|
||||||
|
assert not User.objects.filter(pk=simple_user.pk).exists()
|
||||||
|
assert_event('manager.user.deletion', user=admin, api=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(drf_version.startswith('3.4'), reason='no support for old django rest framework')
|
@pytest.mark.skipif(drf_version.startswith('3.4'), reason='no support for old django rest framework')
|
||||||
def test_api_statistics_list(app, admin):
|
def test_api_statistics_list(app, admin):
|
||||||
OU = get_ou_model()
|
OU = get_ou_model()
|
||||||
|
|
|
@ -79,7 +79,7 @@ def events(db, freezer):
|
||||||
make("user.login.failure", username="agent")
|
make("user.login.failure", username="agent")
|
||||||
make("user.login", user=user, session=session1, how="password")
|
make("user.login", user=user, session=session1, how="password")
|
||||||
make("user.password.change", user=user, session=session1)
|
make("user.password.change", user=user, session=session1)
|
||||||
edit_profile_form = mock.Mock()
|
edit_profile_form = mock.Mock(spec=["instance", "initial", "changed_data", "cleaned_data"])
|
||||||
edit_profile_form.initial = {'email': "user@example.com", 'first_name': "John"}
|
edit_profile_form.initial = {'email': "user@example.com", 'first_name': "John"}
|
||||||
edit_profile_form.changed_data = ["first_name"]
|
edit_profile_form.changed_data = ["first_name"]
|
||||||
edit_profile_form.cleaned_data = {'first_name': "Jane"}
|
edit_profile_form.cleaned_data = {'first_name': "Jane"}
|
||||||
|
@ -1030,6 +1030,15 @@ def test_search(app, superuser, events):
|
||||||
for p in zip(pq('tbody td.journal-list--user-column'), pq('tbody td.journal-list--message-column'))
|
for p in zip(pq('tbody td.journal-list--user-column'), pq('tbody td.journal-list--message-column'))
|
||||||
] == [['Johnny doe', 'login using password']]
|
] == [['Johnny doe', 'login using password']]
|
||||||
|
|
||||||
|
Event.objects.filter(type__name='manager.user.creation').update(api=True)
|
||||||
|
response.form.set('search', 'api:true')
|
||||||
|
response = response.form.submit()
|
||||||
|
assert (
|
||||||
|
text_content(response.pyquery('tbody tr td.journal-list--message-column')[0]).strip()
|
||||||
|
== 'creation of user "Johnny doe"'
|
||||||
|
)
|
||||||
|
assert text_content(response.pyquery('tbody tr td.journal-list--session-column')[0]).strip() == 'API'
|
||||||
|
|
||||||
response.form.set('search', '')
|
response.form.set('search', '')
|
||||||
response.form['event_type'].select(text='Profile changes')
|
response.form['event_type'].select(text='Profile changes')
|
||||||
response = response.form.submit()
|
response = response.form.submit()
|
||||||
|
|
|
@ -264,8 +264,8 @@ def text_content(node):
|
||||||
return ''.join(node.itertext()) if node is not None else ''
|
return ''.join(node.itertext()) if node is not None else ''
|
||||||
|
|
||||||
|
|
||||||
def assert_event(event_type_name, user=None, session=None, service=None, target_user=None, **data):
|
def assert_event(event_type_name, user=None, session=None, service=None, target_user=None, api=False, **data):
|
||||||
qs = Event.objects.filter(type__name=event_type_name)
|
qs = Event.objects.filter(type__name=event_type_name, api=api)
|
||||||
if user:
|
if user:
|
||||||
qs = qs.filter(user=user)
|
qs = qs.filter(user=user)
|
||||||
else:
|
else:
|
||||||
|
@ -281,9 +281,13 @@ def assert_event(event_type_name, user=None, session=None, service=None, target_
|
||||||
if target_user:
|
if target_user:
|
||||||
qs = qs.which_references(target_user)
|
qs = qs.which_references(target_user)
|
||||||
|
|
||||||
assert qs.count() == 1
|
count = qs.count()
|
||||||
|
assert count > 0
|
||||||
|
|
||||||
if data:
|
if not data:
|
||||||
|
assert count == 1
|
||||||
|
|
||||||
|
if data and count == 1:
|
||||||
event = qs.get()
|
event = qs.get()
|
||||||
assert event.data, 'no event.data, should be %s' % data
|
assert event.data, 'no event.data, should be %s' % data
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
|
@ -293,6 +297,8 @@ def assert_event(event_type_name, user=None, session=None, service=None, target_
|
||||||
event.data.get(key),
|
event.data.get(key),
|
||||||
value,
|
value,
|
||||||
)
|
)
|
||||||
|
elif data and count > 1:
|
||||||
|
assert qs.filter(**{'data__' + k: v for k, v in data.items()}).count() == 1
|
||||||
|
|
||||||
|
|
||||||
@httmock.HTTMock
|
@httmock.HTTMock
|
||||||
|
|
Loading…
Reference in New Issue