diff --git a/mayan/apps/acls/api_views.py b/mayan/apps/acls/api_views.py new file mode 100644 index 0000000000..36aece2dd7 --- /dev/null +++ b/mayan/apps/acls/api_views.py @@ -0,0 +1,202 @@ +from __future__ import absolute_import, unicode_literals + +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import PermissionDenied +from django.shortcuts import get_object_or_404 + +from rest_framework import generics + +from permissions import Permission + +from .models import AccessControlList +from .permissions import permission_acl_edit, permission_acl_view +from .serializers import ( + AccessControlListPermissionSerializer, AccessControlListSerializer +) + + +class APIObjectACLListView(generics.ListAPIView): + serializer_class = AccessControlListSerializer + + def get(self, *args, **kwargs): + """ + Returns a list of all the object's access control lists + """ + + return super(APIObjectACLListView, self).get(*args, **kwargs) + + def get_content_object(self): + content_type = get_object_or_404( + ContentType, app_label=self.kwargs['app_label'], + model=self.kwargs['model'] + ) + + content_object = get_object_or_404( + content_type.model_class(), pk=self.kwargs['object_pk'] + ) + + try: + Permission.check_permissions( + self.request.user, permissions=(permission_acl_view,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_acl_view, self.request.user, content_object + ) + + return content_object + + def get_queryset(self): + return self.get_content_object().acls.all() + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + + return { + 'format': self.format_kwarg, + 'request': self.request, + 'view': self + } + + +class APIObjectACLView(generics.RetrieveAPIView): + serializer_class = AccessControlListSerializer + + def get(self, *args, **kwargs): + """ + Returns the details of the selected access control list. + """ + + return super(APIObjectACLView, self).get(*args, **kwargs) + + def get_content_object(self): + if self.request.method == 'GET': + permission_required = permission_acl_view + else: + permission_required = permission_acl_edit + + content_type = get_object_or_404( + ContentType, app_label=self.kwargs['app_label'], + model=self.kwargs['model'] + ) + + content_object = get_object_or_404( + content_type.model_class(), pk=self.kwargs['object_pk'] + ) + + try: + Permission.check_permissions( + self.request.user, permissions=(permission_required,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_required, self.request.user, content_object + ) + + return content_object + + def get_queryset(self): + return self.get_content_object().acls.all() + + +class APIObjectACLPermissionListView(generics.ListAPIView): + serializer_class = AccessControlListPermissionSerializer + + def get(self, *args, **kwargs): + """ + Returns the access control list permission list. + """ + + return super( + APIObjectACLPermissionListView, self + ).get(*args, **kwargs) + + def get_acl(self): + return get_object_or_404( + self.get_content_object().acls, pk=self.kwargs['pk'] + ) + + def get_content_object(self): + content_type = get_object_or_404( + ContentType, app_label=self.kwargs['app_label'], + model=self.kwargs['model'] + ) + + content_object = get_object_or_404( + content_type.model_class(), pk=self.kwargs['object_pk'] + ) + + try: + Permission.check_permissions( + self.request.user, permissions=(permission_acl_view,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_acl_view, self.request.user, content_object + ) + + return content_object + + def get_queryset(self): + return self.get_acl().permissions.all() + + def get_serializer_context(self): + return { + 'acl': self.get_acl(), + 'format': self.format_kwarg, + 'request': self.request, + 'view': self + } + + +class APIObjectACLPermissionView(generics.RetrieveAPIView): + lookup_url_kwarg = 'permission_pk' + serializer_class = AccessControlListPermissionSerializer + + def get(self, *args, **kwargs): + """ + Returns the details of the selected access control list permission. + """ + + return super( + APIObjectACLPermissionView, self + ).get(*args, **kwargs) + + def get_acl(self): + return get_object_or_404( + self.get_content_object().acls, pk=self.kwargs['pk'] + ) + + def get_content_object(self): + content_type = get_object_or_404( + ContentType, app_label=self.kwargs['app_label'], + model=self.kwargs['model'] + ) + + content_object = get_object_or_404( + content_type.model_class(), pk=self.kwargs['object_pk'] + ) + + try: + Permission.check_permissions( + self.request.user, permissions=(permission_acl_view,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_acl_view, self.request.user, content_object + ) + + return content_object + + def get_queryset(self): + return self.get_acl().permissions.all() + + def get_serializer_context(self): + return { + 'acl': self.get_acl(), + 'format': self.format_kwarg, + 'request': self.request, + 'view': self + } diff --git a/mayan/apps/acls/apps.py b/mayan/apps/acls/apps.py index ebce7ca4ce..9f3404e80b 100644 --- a/mayan/apps/acls/apps.py +++ b/mayan/apps/acls/apps.py @@ -4,6 +4,7 @@ from django.utils.translation import ugettext_lazy as _ from common import MayanAppConfig, menu_object, menu_sidebar from navigation import SourceColumn +from rest_api.classes import APIEndPoint from .links import link_acl_create, link_acl_delete, link_acl_permissions @@ -16,6 +17,8 @@ class ACLsApp(MayanAppConfig): def ready(self): super(ACLsApp, self).ready() + APIEndPoint(app=self, version_string='1') + AccessControlList = self.get_model('AccessControlList') SourceColumn( diff --git a/mayan/apps/acls/serializers.py b/mayan/apps/acls/serializers.py new file mode 100644 index 0000000000..72d9163589 --- /dev/null +++ b/mayan/apps/acls/serializers.py @@ -0,0 +1,74 @@ +from __future__ import absolute_import, unicode_literals + +from django.utils.translation import ugettext_lazy as _ + +from rest_framework import serializers +from rest_framework.reverse import reverse + +from common.serializers import ContentTypeSerializer +from permissions.serializers import PermissionSerializer, RoleSerializer + +from .models import AccessControlList + + +class AccessControlListSerializer(serializers.ModelSerializer): + content_type = ContentTypeSerializer(read_only=True) + permissions_url = serializers.SerializerMethodField( + help_text=_( + 'API URL pointing to the list of permissions for this access ' + 'control list.' + ) + ) + role = RoleSerializer(read_only=True) + url = serializers.SerializerMethodField() + + class Meta: + fields = ( + 'content_type', 'id', 'object_id', 'permissions_url', 'role', 'url' + ) + model = AccessControlList + + def get_permissions_url(self, instance): + return reverse( + 'rest_api:accesscontrollist-permission-list', args=( + instance.content_type.app_label, instance.content_type.model, + instance.object_id, instance.pk + ), request=self.context['request'], format=self.context['format'] + ) + + def get_url(self, instance): + return reverse( + 'rest_api:accesscontrollist-detail', args=( + instance.content_type.app_label, instance.content_type.model, + instance.object_id, instance.pk + ), request=self.context['request'], format=self.context['format'] + ) + + +class AccessControlListPermissionSerializer(PermissionSerializer): + acl_permission_url = serializers.SerializerMethodField( + help_text=_( + 'API URL pointing to a permission in relation to the ' + 'access control list to which it is attached. This URL is ' + 'different than the canonical workflow URL.' + ) + ) + + def __init__(self, *args, **kwargs): + super( + AccessControlListPermissionSerializer, self + ).__init__(*args, **kwargs) + + # Make all fields (inherited and local) read ony. + for field in self._readable_fields: + field.read_only = True + + def get_acl_permission_url(self, instance): + return reverse( + 'rest_api:accesscontrollist-permission-detail', args=( + self.context['acl'].content_type.app_label, + self.context['acl'].content_type.model, + self.context['acl'].object_id, self.context['acl'].pk, + instance.stored_permission.pk + ), request=self.context['request'], format=self.context['format'] + ) diff --git a/mayan/apps/acls/tests/test_api.py b/mayan/apps/acls/tests/test_api.py new file mode 100644 index 0000000000..9c7eade47c --- /dev/null +++ b/mayan/apps/acls/tests/test_api.py @@ -0,0 +1,146 @@ +from __future__ import absolute_import, unicode_literals + +from django.contrib.auth import get_user_model +from django.contrib.contenttypes.models import ContentType +from django.core.urlresolvers import reverse +from django.test import override_settings + +from rest_framework.test import APITestCase + +from documents.models import DocumentType +from documents.permissions import permission_document_view +from documents.tests.literals import ( + TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH +) +from permissions.classes import Permission +from permissions.models import Role +from permissions.tests.literals import TEST_ROLE_LABEL +from user_management.tests.literals import ( + TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME +) + +from ..models import AccessControlList + + +@override_settings(OCR_AUTO_OCR=False) +class ACLAPITestCase(APITestCase): + def setUp(self): + self.admin_user = get_user_model().objects.create_superuser( + username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, + password=TEST_ADMIN_PASSWORD + ) + + self.client.login( + username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD + ) + + self.document_type = DocumentType.objects.create( + label=TEST_DOCUMENT_TYPE + ) + + with open(TEST_SMALL_DOCUMENT_PATH) as file_object: + self.document = self.document_type.new_document( + file_object=file_object + ) + + self.role = Role.objects.create(label=TEST_ROLE_LABEL) + + self.document_content_type = ContentType.objects.get_for_model( + self.document + ) + Permission.invalidate_cache() + + def tearDown(self): + if hasattr(self, 'document_type'): + self.document_type.delete() + + def _create_acl(self): + self.acl = AccessControlList.objects.create( + content_object=self.document, + role=self.role + ) + + self.acl.permissions.add(permission_document_view.stored_permission) + + def test_object_acl_list_view(self): + self._create_acl() + + response = self.client.get( + reverse( + 'rest_api:accesscontrollist-list', + args=( + self.document_content_type.app_label, + self.document_content_type.model, + self.document.pk + ) + ) + ) + + self.assertEqual( + response.data['results'][0]['content_type']['app_label'], + self.document_content_type.app_label + ) + self.assertEqual( + response.data['results'][0]['role']['label'], TEST_ROLE_LABEL + ) + + def test_object_acl_detail_view(self): + self._create_acl() + + response = self.client.get( + reverse( + 'rest_api:accesscontrollist-detail', + args=( + self.document_content_type.app_label, + self.document_content_type.model, + self.document.pk, self.acl.pk + ) + ) + ) + + self.assertEqual( + response.data['content_type']['app_label'], + self.document_content_type.app_label + ) + self.assertEqual( + response.data['role']['label'], TEST_ROLE_LABEL + ) + + def test_object_acl_permission_list_view(self): + self._create_acl() + + response = self.client.get( + reverse( + 'rest_api:accesscontrollist-permission-list', + args=( + self.document_content_type.app_label, + self.document_content_type.model, + self.document.pk, self.acl.pk + ) + ) + ) + + self.assertEqual( + response.data['results'][0]['pk'], + permission_document_view.pk + ) + + def test_object_acl_permission_detail_view(self): + self._create_acl() + permission = self.acl.permissions.first() + + response = self.client.get( + reverse( + 'rest_api:accesscontrollist-permission-detail', + args=( + self.document_content_type.app_label, + self.document_content_type.model, + self.document.pk, self.acl.pk, + permission.pk + ) + ) + ) + + self.assertEqual( + response.data['pk'], permission_document_view.pk + ) diff --git a/mayan/apps/acls/urls.py b/mayan/apps/acls/urls.py index f68cc5e0b8..bb52d1a031 100644 --- a/mayan/apps/acls/urls.py +++ b/mayan/apps/acls/urls.py @@ -2,6 +2,10 @@ from __future__ import unicode_literals from django.conf.urls import patterns, url +from .api_views import ( + APIObjectACLListView, APIObjectACLPermissionListView, + APIObjectACLPermissionView, APIObjectACLView +) from .views import ( ACLCreateView, ACLDeleteView, ACLListView, ACLPermissionsView ) @@ -22,3 +26,23 @@ urlpatterns = patterns( name='acl_permissions' ), ) + + +api_urls = [ + url( + r'^object/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/$', + APIObjectACLListView.as_view(), name='accesscontrollist-list' + ), + url( + r'^object/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/$', + APIObjectACLView.as_view(), name='accesscontrollist-detail' + ), + url( + r'^object/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/$', + APIObjectACLPermissionListView.as_view(), name='accesscontrollist-permission-list' + ), + url( + r'^object/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/(?P\d+)/$', + APIObjectACLPermissionView.as_view(), name='accesscontrollist-permission-detail' + ), +]