diff --git a/greenmine/base/auth/api.py b/greenmine/base/auth/api.py index db6750e6..db3e19f7 100644 --- a/greenmine/base/auth/api.py +++ b/greenmine/base/auth/api.py @@ -29,13 +29,17 @@ class AuthViewSet(viewsets.ViewSet): def _create_response(self, user): serializer = UserSerializer(user) response_data = serializer.data + + domain = get_active_domain() + response_data['is_site_owner'] = domain.user_is_owner(user) + response_data['is_site_staff'] = domain.user_is_staff(user) response_data["auth_token"] = auth.get_token_for_user(user) return response_data def _create_domain_member(self, user): domain = get_active_domain() - if DomainMember.objects.filter(domain=domain, user=user).count() == 0: + if domain.members.filter(user=user).count() == 0: domain_member = DomainMember(domain=domain, user=user, email=user.email, is_owner=False, is_staff=False) domain_member.save() diff --git a/greenmine/base/domains/admin.py b/greenmine/base/domains/admin.py index ff35b1ac..d90935f8 100644 --- a/greenmine/base/domains/admin.py +++ b/greenmine/base/domains/admin.py @@ -1,10 +1,13 @@ from django.contrib import admin -from .models import Domain +from .models import Domain, DomainMember +class DomainMemberInline(admin.TabularInline): + model = DomainMember class DomainAdmin(admin.ModelAdmin): list_display = ('domain', 'name') search_fields = ('domain', 'name') + inlines = [ DomainMemberInline, ] admin.site.register(Domain, DomainAdmin) diff --git a/greenmine/base/domains/api.py b/greenmine/base/domains/api.py index dd57a35d..1b3efbdf 100644 --- a/greenmine/base/domains/api.py +++ b/greenmine/base/domains/api.py @@ -2,13 +2,38 @@ from rest_framework import viewsets from rest_framework.response import Response -from rest_framework.permissions import AllowAny +from rest_framework.permissions import AllowAny, IsAuthenticated -from .serializers import DomainSerializer +from django.http import Http404 + +from greenmine.base.api import ModelCrudViewSet, UpdateModelMixin +from .serializers import DomainSerializer, DomainMemberSerializer +from .permissions import DomainMembersPermission, DomainPermission +from .models import DomainMember, Domain -class DomainViewSet(viewsets.ViewSet): - permission_classes = (AllowAny,) +class DomainViewSet(UpdateModelMixin, viewsets.GenericViewSet): + permission_classes = (IsAuthenticated, DomainPermission,) + serializer_class = DomainSerializer + queryset = Domain.objects.all() def list(self, request, **kwargs): - return Response(DomainSerializer(request.domain).data) + domain_data = DomainSerializer(request.domain).data + if request.domain.user_is_normal_user(request.user): + domain_data['projects'] = None + elif request.user.is_anonymous(): + domain_data['projects'] = None + return Response(domain_data) + + def update(self, request, **kwargs): + raise Http404 + + def create(self, request, **kwargs): + self.kwargs['pk'] = request.domain.pk + return super().update(request, pk=request.domain.pk, **kwargs) + + +class DomainMembersViewSet(ModelCrudViewSet): + permission_classes = (IsAuthenticated, DomainMembersPermission,) + serializer_class = DomainMemberSerializer + queryset = DomainMember.objects.all() diff --git a/greenmine/base/domains/models.py b/greenmine/base/domains/models.py index 2b730aa0..c550d062 100644 --- a/greenmine/base/domains/models.py +++ b/greenmine/base/domains/models.py @@ -45,9 +45,18 @@ class Domain(models.Model): def __str__(self): return self.domain + def user_is_owner(self, user): + return self.members.filter(id=user.id, is_owner=True).count() > 0 + + def user_is_staff(self, user): + return self.members.filter(id=user.id, is_staff=True).count() > 0 + + def user_is_normal_user(self, user): + return self.members.filter(id=user.id, is_owner=False, is_staff=False).count() > 0 + class DomainMember(models.Model): - domain = models.ForeignKey("Domain", related_name="+", null=True) + domain = models.ForeignKey("Domain", related_name="members", null=True) user = models.ForeignKey("users.User", related_name="+", null=True) email = models.EmailField(max_length=255) diff --git a/greenmine/base/domains/permissions.py b/greenmine/base/domains/permissions.py new file mode 100644 index 00000000..1fcba466 --- /dev/null +++ b/greenmine/base/domains/permissions.py @@ -0,0 +1,29 @@ +from rest_framework import permissions + +from greenmine.base.domains.models import DomainMember +from greenmine.base.domains import get_active_domain + + +class DomainPermission(permissions.BasePermission): + safe_methods = ['HEAD', 'OPTIONS', 'GET'] + + def has_object_permission(self, request, view, obj): + if request.method in self.safe_methods: + return True + + domain = get_active_domain() + return domain.user_is_owner(request.user) + + +class DomainMembersPermission(permissions.BasePermission): + safe_methods = ['HEAD', 'OPTIONS'] + + def has_permission(self, request, view): + if request.method in self.safe_methods: + return True + + domain = get_active_domain() + if request.method in ["POST", "PUT", "PATCH", "GET"]: + return domain.user_is_owner(request.user) + else: + return False diff --git a/greenmine/base/domains/serializers.py b/greenmine/base/domains/serializers.py index 8852060c..edf1ff08 100644 --- a/greenmine/base/domains/serializers.py +++ b/greenmine/base/domains/serializers.py @@ -1,10 +1,22 @@ # -*- coding: utf-8 -*- from rest_framework import serializers -from .models import Domain +from .models import Domain, DomainMember +from greenmine.base.users.serializers import UserSerializer class DomainSerializer(serializers.ModelSerializer): + projects = serializers.SerializerMethodField('get_projects') + class Meta: model = Domain - fields = ('public_register', 'default_language') + fields = ('public_register', 'default_language', "projects") + + def get_projects(self, obj): + return map(lambda x: {"id": x.id, "name": x.name }, obj.projects.all().order_by('name')) + + +class DomainMemberSerializer(serializers.ModelSerializer): + user = UserSerializer() + class Meta: + model = DomainMember diff --git a/greenmine/projects/api.py b/greenmine/projects/api.py index 92b0c67d..b9e1f430 100644 --- a/greenmine/projects/api.py +++ b/greenmine/projects/api.py @@ -16,6 +16,7 @@ from djmail.template_mail import MagicMailBuilder from greenmine.base import filters from greenmine.base import exceptions as exc from greenmine.base.api import ModelCrudViewSet, ModelListViewSet, RetrieveModelMixin +from greenmine.base.domains import get_active_domain from greenmine.base.notifications.api import NotificationSenderMixin from greenmine.projects.aggregates.tags import get_all_tags @@ -27,6 +28,29 @@ from .aggregates import stats from .aggregates import filters as filters_aggr +class ProjectAdminViewSet(ModelCrudViewSet): + model = models.Project + serializer_class = serializers.ProjectDetailSerializer + list_serializer_class = serializers.ProjectSerializer + permission_classes = (IsAuthenticated, permissions.ProjectAdminPermission) + + def get_queryset(self): + domain = get_active_domain() + return domain.projects.all() + + def pre_save(self, obj): + obj.owner = self.request.user + + # FIXME + + # Assign domain only if it current + # value is None + if not obj.domain: + obj.domain = self.request.domain + + super().pre_save(obj) + + class ProjectViewSet(ModelCrudViewSet): model = models.Project serializer_class = serializers.ProjectDetailSerializer diff --git a/greenmine/projects/permissions.py b/greenmine/projects/permissions.py index fcb2b128..bb69670f 100644 --- a/greenmine/projects/permissions.py +++ b/greenmine/projects/permissions.py @@ -1,17 +1,41 @@ # -*- coding: utf-8 -*- from greenmine.base.permissions import BasePermission +from greenmine.base.domains import get_active_domain class ProjectPermission(BasePermission): get_permission = "view_project" - post_permission = "add_project" + post_permission = None put_permission = "change_project" patch_permission = "change_project" - delete_permission = "delete_project" + delete_permission = None safe_methods = ["HEAD", "OPTIONS"] path_to_project = [] +class ProjectAdminPermission(BasePermission): + def has_permission(self, request, view): + if request.method in self.safe_methods: + return True + + domain = get_active_domain() + if request.method in ["POST", "PUT", "GET", "PATCH"]: + return domain.user_is_staff(request.user) + elif request.method == "DELETE": + return domain.user_is_owner(request.user) + return super().has_permission(request, view) + + def has_object_permission(self, request, view, obj): + if request.method in self.safe_methods: + return True + + domain = get_active_domain() + if request.method in ["POST", "PUT", "GET", "PATCH"]: + return domain.user_is_staff(request.user) + elif request.method == "DELETE": + return domain.user_is_owner(request.user) + return super().has_object_permission(request, view, obj) + class MembershipPermission(BasePermission): get_permission = "view_membership" diff --git a/greenmine/projects/tests/tests_api.py b/greenmine/projects/tests/tests_api.py index 63134bb3..f55cef95 100644 --- a/greenmine/projects/tests/tests_api.py +++ b/greenmine/projects/tests/tests_api.py @@ -418,8 +418,8 @@ class ProjectsTestCase(test.TestCase): password=self.user3.username) self.assertTrue(response) response = self.client.delete(reverse("projects-detail", args=(self.project1.id,))) - self.assertEqual(response.status_code, 204) - self.assertEqual(Project.objects.all().count(), 3) + self.assertEqual(response.status_code, 403) + self.assertEqual(Project.objects.all().count(), 4) self.client.logout() def test_delete_project_by_not_membership(self): diff --git a/greenmine/routers.py b/greenmine/routers.py index 767a4a64..1b160267 100644 --- a/greenmine/routers.py +++ b/greenmine/routers.py @@ -4,11 +4,11 @@ from greenmine.base import routers from greenmine.base.auth.api import AuthViewSet from greenmine.base.users.api import RolesViewSet, UsersViewSet from greenmine.base.searches.api import SearchViewSet -from greenmine.base.domains.api import DomainViewSet +from greenmine.base.domains.api import DomainViewSet, DomainMembersViewSet from greenmine.projects.api import (ProjectViewSet, MembershipViewSet, InvitationViewSet, UserStoryStatusViewSet, PointsViewSet, TaskStatusViewSet, IssueStatusViewSet, IssueTypeViewSet, PriorityViewSet, - SeverityViewSet) #, QuestionStatusViewSet) + SeverityViewSet, ProjectAdminViewSet) #, QuestionStatusViewSet) from greenmine.projects.milestones.api import MilestoneViewSet from greenmine.projects.userstories.api import UserStoryViewSet, UserStoryAttachmentViewSet from greenmine.projects.tasks.api import TaskViewSet, TaskAttachmentViewSet @@ -30,6 +30,8 @@ router.register(r"search", SearchViewSet, base_name="search") # greenmine.base.domains router.register(r"sites", DomainViewSet, base_name="sites") +router.register(r"site-members", DomainMembersViewSet, base_name="site-members") +router.register(r"site-projects", ProjectAdminViewSet, base_name="site-projects") # greenmine.projects router.register(r"projects", ProjectViewSet, base_name="projects")