Skip to content

Commit

Permalink
Merge 7e29675 into 8d7461f
Browse files Browse the repository at this point in the history
  • Loading branch information
Douglas Paz committed Jun 27, 2018
2 parents 8d7461f + 7e29675 commit bf92f64
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 5 deletions.
7 changes: 7 additions & 0 deletions bothub/api/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
from .views import Categories
from .views import RepositoriesViewSet
from .views import TranslationsViewSet
from .views import RepositoryAuthorizationViewSet
from .views import RepositoryAuthorizationRoleViewSet
from .views import SearchUserViewSet


class Router(routers.SimpleRouter):
Expand Down Expand Up @@ -110,3 +113,7 @@ def get_lookup_regex(self, viewset, lookup_prefix=''):
router.register('categories', Categories)
router.register('repositories', RepositoriesViewSet)
router.register('translations', TranslationsViewSet)
router.register('list-authorizations', RepositoryAuthorizationViewSet)
router.register('authorization-role',
RepositoryAuthorizationRoleViewSet)
router.register('search-user', SearchUserViewSet)
1 change: 1 addition & 0 deletions bothub/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
AnalyzeTextSerializer,
EditRepositorySerializer,
VoteSerializer,
RepositoryAuthorizationRoleSerializer,
)

from .category import ( # noqa: F401
Expand Down
16 changes: 16 additions & 0 deletions bothub/api/serializers/repository.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied
from django.utils.translation import gettext as _

from bothub.common.models import Repository
from bothub.common.models import RepositoryCategory
Expand Down Expand Up @@ -100,6 +102,7 @@ class Meta:
'uuid',
'user',
'repository',
'role',
'level',
'can_read',
'can_contribute',
Expand All @@ -120,3 +123,16 @@ class Meta:
fields = [
'vote',
]


class RepositoryAuthorizationRoleSerializer(serializers.ModelSerializer):
class Meta:
model = RepositoryAuthorization
fields = [
'role',
]

def validate(self, data):
if self.instance.user == self.instance.repository.owner:
raise PermissionDenied(_('The owner role can\'t be changed.'))
return data
1 change: 0 additions & 1 deletion bothub/api/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class Meta:
model = User
fields = [
'nickname',
'email',
'name',
'locale',
]
Expand Down
145 changes: 145 additions & 0 deletions bothub/api/tests/test_authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

from django.test import TestCase
from django.test import RequestFactory
from django.test.client import MULTIPART_CONTENT
from rest_framework import status

from bothub.common import languages
from bothub.common.models import Repository
from bothub.common.models import RepositoryAuthorization

from ..views import RepositoryViewSet
from ..views import RepositoryAuthorizationViewSet
from ..views import RepositoryAuthorizationRoleViewSet

from .utils import create_user_and_token

Expand Down Expand Up @@ -90,3 +94,144 @@ def test_user_forbidden_private(self):
self.assertEqual(
response.status_code,
status.HTTP_403_FORBIDDEN)


class ListAuthorizationTestCase(TestCase):
def setUp(self):
self.factory = RequestFactory()

self.owner, self.owner_token = create_user_and_token('owner')
self.user, self.user_token = create_user_and_token()

self.repository = Repository.objects.create(
owner=self.owner,
name='Testing',
slug='test',
language=languages.LANGUAGE_EN)

self.user_auth = self.repository.get_user_authorization(self.user)
self.user_auth.role = RepositoryAuthorization.ROLE_CONTRIBUTOR
self.user_auth.save()

def request(self, repository, token):
authorization_header = {
'HTTP_AUTHORIZATION': 'Token {}'.format(token.key),
}
request = self.factory.get(
'/api/list-authorizations/',
{
'repository': repository.uuid,
},
**authorization_header)
response = RepositoryAuthorizationViewSet.as_view(
{'get': 'list'})(request)
response.render()
content_data = json.loads(response.content)
return (response, content_data,)

def test_okay(self):
response, content_data = self.request(
self.repository,
self.owner_token)

self.assertEqual(
response.status_code,
status.HTTP_200_OK)

self.assertEqual(
content_data.get('count'),
1)

self.assertEqual(
content_data.get('results')[0].get('user'),
self.user.id)

def test_user_forbidden(self):
response, content_data = self.request(
self.repository,
self.user_token)

self.assertEqual(
response.status_code,
status.HTTP_403_FORBIDDEN)


class UpdateAuthorizationRoleTestCase(TestCase):
def setUp(self):
self.factory = RequestFactory()

self.owner, self.owner_token = create_user_and_token('owner')
self.user, self.user_token = create_user_and_token()

self.repository = Repository.objects.create(
owner=self.owner,
name='Testing',
slug='test',
language=languages.LANGUAGE_EN)

def request(self, repository, token, user, data):
authorization_header = {
'HTTP_AUTHORIZATION': 'Token {}'.format(token.key),
}
request = self.factory.patch(
'/api/authorization-role/{}/{}/'.format(
repository.uuid, user.nickname),
self.factory._encode_data(data, MULTIPART_CONTENT),
MULTIPART_CONTENT,
**authorization_header)
view = RepositoryAuthorizationRoleViewSet.as_view(
{'patch': 'update'})
response = view(
request,
repository__uuid=repository.uuid,
user__nickname=user.nickname)
response.render()
content_data = json.loads(response.content)
return (response, content_data,)

def test_okay(self):
response, content_data = self.request(
self.repository,
self.owner_token,
self.user,
{
'role': RepositoryAuthorization.ROLE_CONTRIBUTOR,
})

self.assertEqual(
response.status_code,
status.HTTP_200_OK)
self.assertEqual(
content_data.get('role'),
RepositoryAuthorization.ROLE_CONTRIBUTOR)

user_authorization = self.repository.get_user_authorization(self.user)
self.assertEqual(
user_authorization.role,
RepositoryAuthorization.ROLE_CONTRIBUTOR)

def test_forbidden(self):
response, content_data = self.request(
self.repository,
self.user_token,
self.user,
{
'role': RepositoryAuthorization.ROLE_CONTRIBUTOR,
})

self.assertEqual(
response.status_code,
status.HTTP_403_FORBIDDEN)

def test_owner_can_t_set_your_role(self):
response, content_data = self.request(
self.repository,
self.owner_token,
self.owner,
{
'role': RepositoryAuthorization.ROLE_CONTRIBUTOR,
})

self.assertEqual(
response.status_code,
status.HTTP_403_FORBIDDEN)
99 changes: 99 additions & 0 deletions bothub/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from bothub.common.models import RepositoryTranslatedExampleEntity
from bothub.common.models import RepositoryCategory
from bothub.common.models import RepositoryVote
from bothub.common.models import RepositoryAuthorization
from bothub.authentication.models import User

from .serializers import RepositorySerializer
Expand All @@ -45,6 +46,7 @@
from .serializers import EditRepositorySerializer
from .serializers import NewRepositoryTranslatedExampleSerializer
from .serializers import VoteSerializer
from .serializers import RepositoryAuthorizationRoleSerializer


# Permisions
Expand Down Expand Up @@ -94,6 +96,12 @@ def has_object_permission(self, request, view, obj):
return authorization.can_contribute


class RepositoryAdminManagerAuthorization(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
authorization = obj.repository.get_user_authorization(request.user)
return authorization.is_admin


# Filters

class ExamplesFilter(filters.FilterSet):
Expand Down Expand Up @@ -222,6 +230,31 @@ def filter_to_language(self, queryset, name, value):
return queryset.filter(language=value)


class RepositoryAuthorizationFilter(filters.FilterSet):
class Meta:
model = RepositoryAuthorization
fields = ['repository']

repository = filters.CharFilter(
name='repository',
method='filter_repository_uuid',
help_text=_('Repository\'s UUID'))

def filter_repository_uuid(self, queryset, name, value):
request = self.request
try:
repository = Repository.objects.get(uuid=value)
authorization = repository.get_user_authorization(request.user)
if not authorization.is_admin:
raise PermissionDenied()
return queryset.filter(repository=repository)
except Repository.DoesNotExist:
raise NotFound(
_('Repository {} does not exist').format(value))
except DjangoValidationError:
raise NotFound(_('Invalid repository UUID'))


# Mixins

class MultipleFieldLookupMixin(object):
Expand Down Expand Up @@ -753,3 +786,69 @@ class TranslationsViewSet(
serializer_class = RepositoryTranslatedExampleSerializer
queryset = RepositoryTranslatedExample.objects.all()
filter_class = TranslationsFilter


class RepositoryAuthorizationViewSet(
mixins.ListModelMixin,
GenericViewSet):
queryset = RepositoryAuthorization.objects.exclude(
role=RepositoryAuthorization.ROLE_NOT_SETTED)
serializer_class = RepositoryAuthorizationSerializer
filter_class = RepositoryAuthorizationFilter
permission_classes = [
IsAuthenticated,
]


class RepositoryAuthorizationRoleViewSet(
MultipleFieldLookupMixin,
mixins.UpdateModelMixin,
GenericViewSet):
queryset = RepositoryAuthorization.objects.exclude(
role=RepositoryAuthorization.ROLE_NOT_SETTED)
lookup_field = 'user__nickname'
lookup_fields = ['repository__uuid', 'user__nickname']
serializer_class = RepositoryAuthorizationRoleSerializer
permission_classes = [
IsAuthenticated,
RepositoryAdminManagerAuthorization,
]

def get_object(self):
repository_uuid = self.kwargs.get('repository__uuid')
user_nickname = self.kwargs.get('user__nickname')

repository = get_object_or_404(Repository, uuid=repository_uuid)
user = get_object_or_404(User, nickname=user_nickname)

obj = repository.get_user_authorization(user)

self.check_object_permissions(self.request, obj)
return obj


class SearchUserViewSet(
mixins.ListModelMixin,
GenericViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
filter_backends = [
DjangoFilterBackend,
SearchFilter,
]
search_fields = [
'=name',
'^name',
'$name',
'=nickname',
'^nickname',
'$nickname',
'=email',
]
pagination_class = None
limit = 5

def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())[:self.limit]
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
18 changes: 18 additions & 0 deletions bothub/common/migrations/0011_repositoryauthorization_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-06-21 12:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('common', '0010_auto_20180611_1123'),
]

operations = [
migrations.AddField(
model_name='repositoryauthorization',
name='role',
field=models.PositiveIntegerField(choices=[(0, 'not set'), (1, 'user'), (2, 'contributor'), (3, 'admin')], default=0, verbose_name='role'),
),
]
Loading

0 comments on commit bf92f64

Please sign in to comment.