0001-WIP-add-journal-application-20695.patch
MANIFEST.in | ||
---|---|---|
22 | 22 |
recursive-include src/authentic2_auth_saml/templates/authentic2_auth_saml *.html |
23 | 23 |
recursive-include src/authentic2_auth_oidc/templates/authentic2_auth_oidc *.html |
24 | 24 |
recursive-include src/authentic2_idp_oidc/templates/authentic2_idp_oidc *.html |
25 |
recursive-include src/authentic2_journal/templates/authentic2_journal *.html |
|
25 | 26 | |
26 | 27 |
recursive-include src/authentic2/vendor/totp_js/js *.js |
27 | 28 |
recursive-include src/authentic2/saml/fixtures *.json |
... | ... | |
41 | 42 |
recursive-include src/authentic2_auth_saml/locale *.po *.mo |
42 | 43 |
recursive-include src/authentic2_auth_oidc/locale *.po *.mo |
43 | 44 |
recursive-include src/authentic2_idp_oidc/locale *.po *.mo |
45 |
recursive-include src/authentic2_journal/locale *.po *.mo |
|
44 | 46 | |
45 | 47 |
recursive-include src/authentic2 README xrds.xml *.txt yadis.xrdf |
46 | 48 |
recursive-include src/authentic2_provisionning_ldap/tests *.ldif |
setup.py | ||
---|---|---|
165 | 165 |
'authentic2-idp-cas = authentic2_idp_cas:Plugin', |
166 | 166 |
'authentic2-idp-oidc = authentic2_idp_oidc:Plugin', |
167 | 167 |
'authentic2-provisionning-ldap = authentic2_provisionning_ldap:Plugin', |
168 |
'authentic2-journal = authentic2_journal:Plugin', |
|
168 | 169 |
], |
169 | 170 |
}) |
src/authentic2_journal/__init__.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
class Plugin(object): |
|
3 |
def get_after_urls(self): |
|
4 |
from django.conf.urls import include, url |
|
5 | ||
6 |
return [url(r'^manager/journal/', include(__name__ + '.urls'))] |
|
7 | ||
8 |
def get_apps(self): |
|
9 |
return [__name__] |
src/authentic2_journal/admin.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.contrib import admin |
|
5 | ||
6 |
from authentic2_journal import models |
|
7 | ||
8 | ||
9 |
class JournalAdmin(admin.ModelAdmin): |
|
10 |
list_display = ('label', 'kind') |
|
11 | ||
12 | ||
13 |
admin.site.register(models.Journal, JournalAdmin) |
src/authentic2_journal/api_serializers.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from rest_framework import serializers |
|
3 | ||
4 |
from authentic2_journal import models |
|
5 | ||
6 |
class ReferenceSerializer(serializers.ModelSerializer): |
|
7 | ||
8 |
class Meta: |
|
9 |
model = models.Reference |
|
10 | ||
11 |
# TODO |
|
12 | ||
13 | ||
14 |
class JournalSerializer(serializers.ModelSerializer): |
|
15 | ||
16 |
class Meta: |
|
17 |
model = models.Journal |
|
18 | ||
19 |
# TODO |
src/authentic2_journal/api_urls.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 | ||
3 |
from authentic2_journal import api_views |
|
4 | ||
5 |
urlpattern = api_views.router.urls |
src/authentic2_journal/api_views.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 | ||
3 |
from authentic2_journal import serializers |
|
4 |
from rest_framework import viewsets |
|
5 |
from rest_framework import routers |
|
6 | ||
7 | ||
8 |
class ReferenceViewSet(viewsets.ModelViewSet): |
|
9 |
serializer_class = serializers.ReferenceSerializer |
|
10 |
lookup_field = 'pk' |
|
11 | ||
12 |
# TODO |
|
13 | ||
14 | ||
15 |
class JournalViewSet(viewsets.ModelViewSet): |
|
16 |
serializer_class = serializers.JournalSerializer |
|
17 |
lookup_field = 'pk' |
|
18 | ||
19 |
# TODO |
|
20 | ||
21 |
router = routers.SimpleRouter() |
|
22 |
router.register(r'references', ReferenceViewSet, base_name='a2-journal-references') |
|
23 |
router.register(r'journals', JournalViewSet, base_name='a2-journal-journals') |
src/authentic2_journal/apps.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.apps import AppConfig |
|
5 | ||
6 | ||
7 |
class Authentic2JournalConfig(AppConfig): |
|
8 |
name = 'authentic2_journal' |
src/authentic2_journal/logger.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.db import models |
|
5 | ||
6 |
from authentic2_journal.models import Line |
|
7 | ||
8 |
class BaseLogger(models.Model): |
|
9 |
@classmethod |
|
10 |
def _log(cls, user, obj, content=None): |
|
11 |
Line.objects.create( |
|
12 |
kind=__name__, |
|
13 |
target_ct=obj.__class, |
|
14 |
target_id=obj.pk, |
|
15 |
content=content) |
|
16 | ||
17 | ||
18 |
class AuthenticationLogger(BaseLogger): |
|
19 |
@classmethod |
|
20 |
def sso(cls, user, service, content=None): |
|
21 |
cls._log(user=user, obj=service, content=content) |
|
22 | ||
23 |
@classmethod |
|
24 |
def login(cls, user, provider, content=None): |
|
25 |
cls._log(user=user, obj=provider, content=content) |
|
26 | ||
27 |
@classmethod |
|
28 |
def logout(cls, user, provider, content=None): |
|
29 |
cls._log(user=user, obj=provider, content=content) |
|
30 | ||
31 |
@classmethod |
|
32 |
def register(cls, user, ou, content=None): |
|
33 |
cls._log(user=user, obj=ou, content=content) |
|
34 | ||
35 | ||
36 |
class ModificationLogger(BaseLogger): |
|
37 |
@classmethod |
|
38 |
def create(cls, user, obj, content=None): |
|
39 |
cls._log(user=user, obj=obj, content=content) |
|
40 | ||
41 |
@classmethod |
|
42 |
def update(cls, user, obj, content=None): |
|
43 |
cls._log(user=user, obj=obj, content=content) |
|
44 | ||
45 |
@classmethod |
|
46 |
def modify_members(cls, user, obj, content=None): |
|
47 |
cls._log(user=user, obj=obj, content=content) |
|
48 | ||
49 |
@classmethod |
|
50 |
def delete(cls, user, obj, content=None): |
|
51 |
cls._log(user=user, obj=obj, content=content) |
|
52 | ||
53 |
class GenericLogger(BaseLogger): |
|
54 |
@classmethod |
|
55 |
def generic_event(cls, user, obj, content=None): |
|
56 |
cls._log(user=user, obj=obj, content=content) |
src/authentic2_journal/models.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.db import models |
|
5 |
try: |
|
6 |
from django.contrib.contenttypes.fields import GenericForeignKey |
|
7 |
except ImportError: |
|
8 |
from django.contrib.contenttypes.generic import GenericForeignKey |
|
9 |
from django.contrib.contenttypes.models import ContentType |
|
10 |
from django.utils.translation import ugettext_lazy as _ |
|
11 |
from jsonfield import JSONField |
|
12 | ||
13 |
from . import logger |
|
14 | ||
15 | ||
16 |
KINDS = { |
|
17 |
'authentication': { |
|
18 |
'members': { |
|
19 |
'register': 1, |
|
20 |
'login': 2, |
|
21 |
'sso': 3, |
|
22 |
'logout': 4, |
|
23 |
}, |
|
24 |
'logger': logger.AuthenticationLogger, |
|
25 |
}, |
|
26 |
'modification': { |
|
27 |
'members': { |
|
28 |
'create': 5, |
|
29 |
'update': 6, |
|
30 |
'modify_members': 7, |
|
31 |
'delete': 8, |
|
32 |
}, |
|
33 |
'logger': logger.ModificationLogger, |
|
34 |
}, |
|
35 |
'default': { |
|
36 |
'members': { |
|
37 |
'generic_event': 9, |
|
38 |
} |
|
39 |
} |
|
40 |
} |
|
41 | ||
42 | ||
43 |
class Line(models.Model): |
|
44 |
timestamp = models.DateTimeField(auto_now=True, |
|
45 |
verbose_name=_('timestamp')) |
|
46 |
kind = models.PositiveIntegerField(default=0, |
|
47 |
choices={map(kind['members'].keys(), kind['members'].values()) for key, kind in KINDS.items()}) |
|
48 |
# message = models.TextField() |
|
49 |
content = JSONField(blank=True,null=True, |
|
50 |
verbose_name=_('content')) |
|
51 | ||
52 |
def __init(self, *args, **kwargs): |
|
53 |
from . import Reference |
|
54 | ||
55 |
super(Line, self).__init(*args, **kwargs) |
|
56 |
Reference.objects.create(self) |
|
57 | ||
58 | ||
59 |
class Reference(models.Model): |
|
60 |
timestamp = models.DateTimeField(auto_now=True, |
|
61 |
verbose_name=_('timestamp')) |
|
62 |
line = models.ForeignKey(Line) |
|
63 |
representation = models.TextField(max_length=64) |
|
64 |
target_ct = models.ForeignKey(ContentType) |
|
65 |
target_id = models.IntegerField(default=0) |
|
66 |
target = GenericForeignKey('target_ct', 'target_id') |
|
67 | ||
68 |
def __init__(self, line, *args, **kwargs): |
|
69 |
super(Reference, self).__init__(*args, **kwargs) |
|
70 |
# self.timestamp = line.timestamp # let auto_now ? XXX |
|
71 |
self.line = line |
|
72 | ||
73 | ||
74 |
class Journal(models.Model): |
|
75 |
""" |
|
76 |
# usage: |
|
77 |
authentication_journal = Journal.objects.get_or_create(kind="authentication") |
|
78 |
authentication_journal.log.sso(user, service, content=None) |
|
79 |
""" |
|
80 |
from authentic2_journal import BaseLogger |
|
81 | ||
82 |
log = models.ForeignKey(BaseLogger) |
|
83 | ||
84 |
def __init__(self, *args, **kwargs): |
|
85 |
super(Journal, self).__init__(*args, **kwargs) |
|
86 |
kind = kwargs.get('kind') |
|
87 |
if kind and kind in KINDS: |
|
88 |
self.log = KINDS[kind].get('logger', GenericLogger) |
|
89 |
else: |
|
90 |
self.log = GenericLogger |
src/authentic2_journal/tables.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
import django_tables2 as tables |
|
3 | ||
4 |
from .models import Reference |
|
5 | ||
6 | ||
7 |
class UserActionsTable(tables.Table): |
|
8 |
class Meta: |
|
9 |
attrs = {'class': 'main'} |
|
10 |
model = Reference |
|
11 |
exclude = ['id', 'actor'] |
|
12 |
empty_text = _('None') |
|
13 | ||
14 | ||
15 |
class UserModificationsTable(tables.Table): |
|
16 |
class Meta: |
|
17 |
attrs = {'class': 'main'} |
|
18 |
model = Reference |
|
19 |
exclude = ['id', 'subject'] |
|
20 |
empty_text = _('None') |
src/authentic2_journal/templates/authentic2_journal/user_actions.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/base.html" %} |
|
2 |
{% load i18n staticfiles django_tables2 %} |
|
3 | ||
4 |
{% block page_title %} |
|
5 |
{% trans "User actions journal" %} |
|
6 |
{% endblock %} |
|
7 | ||
8 |
{% block content %} |
|
9 |
{% render_table table "authentic2/manager/table.html" %} |
|
10 |
{% endblock %} |
src/authentic2_journal/templates/authentic2_journal/user_modifications.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/base.html" %} |
|
2 |
{% load i18n staticfiles django_tables2 %} |
|
3 | ||
4 |
{% block page_title %} |
|
5 |
{% trans "User modifications journal" %} |
|
6 |
{% endblock %} |
|
7 | ||
8 |
{% block content %} |
|
9 |
{% render_table table "authentic2/manager/table.html" %} |
|
10 |
{% endblock %} |
src/authentic2_journal/urls.py | ||
---|---|---|
1 |
from django.conf.urls import url |
|
2 | ||
3 |
from . import views |
|
4 | ||
5 | ||
6 |
urlpatterns = [ |
|
7 |
url('^users/(?P<pk>\d+)/actions/$', |
|
8 |
views.user_actions_journal, |
|
9 |
name='a2-journal-user-actions'), |
|
10 |
url('^users/(?P<pk>\d+)/modifications/$', |
|
11 |
views.user_modifications_journal, |
|
12 |
name='a2-journal-user-modifications'), |
|
13 |
] |
src/authentic2_journal/views.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.contrib.auth import get_user_model |
|
5 | ||
6 |
from authentic2.views import SimpleSubTableView |
|
7 |
from .tables import UserActionsTable, UserModificationsTable |
|
8 | ||
9 | ||
10 |
class UserActionsJournal(SimpleSubTableView): |
|
11 |
model = get_user_model() |
|
12 |
table_class = UserActionsTable |
|
13 |
template_name = 'authentic2/manager/user_actions.html' |
|
14 |
permissions = ['custom_user.view_user'] |
|
15 |
filter_table_by_perm = False |
|
16 | ||
17 |
def get_table_queryset(self): |
|
18 |
return [] # XXX |
|
19 | ||
20 | ||
21 |
user_actions_journal = UserActionsJournal.as_view() |
|
22 | ||
23 | ||
24 |
class UserModificationsJournal(SimpleSubTableView): |
|
25 |
model = get_user_model() |
|
26 |
table_class = UserModificationsTable |
|
27 |
template_name = 'authentic2_journal/user_modifications.html' |
|
28 |
permissions = ['custom_user.view_user'] |
|
29 |
filter_table_by_perm = False |
|
30 | ||
31 |
def get_table_queryset(self): |
|
32 |
return [] # XXX |
|
33 | ||
34 | ||
35 |
user_modifications_journal = UserModificationsJournal.as_view() |
tests/test_api.py | ||
---|---|---|
934 | 934 |
assert response.json['checks'][3]['result'] is True |
935 | 935 |
assert response.json['checks'][4]['label'] == 'must contain "ok"' |
936 | 936 |
assert response.json['checks'][4]['result'] is True |
937 | ||
938 | ||
939 |
def test_user_actions_api(settings, db, simple_user, user_ou1, ou1): |
|
940 |
# TODO |
|
941 |
pass |
|
942 | ||
943 | ||
944 |
def test_user_modifications_api(settings, db, simple_user, user_ou1, ou1): |
|
945 |
# TODO |
|
946 |
pass |
|
947 | ||
948 | ||
949 |
def test_journal_api(settings, db, simple_user, user_ou1, ou1): |
|
950 |
# TODO |
|
951 |
pass |
tests/test_views.py | ||
---|---|---|
46 | 46 |
response = app.get(reverse('account_management')) |
47 | 47 |
assert response.status_code == 302 |
48 | 48 |
assert response['Location'] == settings.A2_ACCOUNTS_URL |
49 | ||
50 | ||
51 |
def test_user_actions_view(settings, db, simple_user, user_ou1, ou1): |
|
52 |
# TODO |
|
53 |
pass |
|
54 | ||
55 | ||
56 |
def test_user_modifications_view(settings, db, simple_user, user_ou1, ou1): |
|
57 |
# TODO |
|
58 |
pass |
|
49 |
- |