Merge pull request #61 from taigaio/permissions

[HUGE CHANGE] Changed the permissions system
remotes/origin/enhancement/email-actions
Jesús Espino 2014-07-24 12:32:39 +02:00
commit c7ecd622e1
83 changed files with 7950 additions and 716 deletions

View File

@ -21,11 +21,10 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework import status from rest_framework import status
from rest_framework import viewsets
from rest_framework import serializers from rest_framework import serializers
from taiga.base.api import viewsets
from taiga.base.decorators import list_route from taiga.base.decorators import list_route
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base.connectors import github from taiga.base.connectors import github
@ -41,6 +40,8 @@ from .services import public_register
from .services import github_register from .services import github_register
from .services import make_auth_response_data from .services import make_auth_response_data
from .permissions import AuthPermission
def _parse_data(data:dict, *, cls): def _parse_data(data:dict, *, cls):
""" """
@ -95,7 +96,7 @@ def parse_register_type(userdata:dict) -> str:
class AuthViewSet(viewsets.ViewSet): class AuthViewSet(viewsets.ViewSet):
permission_classes = (AllowAny,) permission_classes = (AuthPermission,)
def _public_register(self, request): def _public_register(self, request):
if not settings.PUBLIC_REGISTER_ENABLED: if not settings.PUBLIC_REGISTER_ENABLED:
@ -123,8 +124,10 @@ class AuthViewSet(viewsets.ViewSet):
data = make_auth_response_data(user) data = make_auth_response_data(user)
return Response(data, status=status.HTTP_201_CREATED) return Response(data, status=status.HTTP_201_CREATED)
@list_route(methods=["POST"], permission_classes=[AllowAny]) @list_route(methods=["POST"])
def register(self, request, **kwargs): def register(self, request, **kwargs):
self.check_permissions(request, 'register', None)
type = request.DATA.get("type", None) type = request.DATA.get("type", None)
if type == "public": if type == "public":
return self._public_register(request) return self._public_register(request)
@ -157,6 +160,8 @@ class AuthViewSet(viewsets.ViewSet):
# Login view: /api/v1/auth # Login view: /api/v1/auth
def create(self, request, **kwargs): def create(self, request, **kwargs):
self.check_permissions(request, 'create', None)
type = request.DATA.get("type", None) type = request.DATA.get("type", None)
if type == "normal": if type == "normal":
return self._login(request) return self._login(request)

22
taiga/auth/permissions.py Normal file
View File

@ -0,0 +1,22 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be> # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import ResourcePermission, AllowAny
class AuthPermission(ResourcePermission):
create_perms = AllowAny()
register_perms = AllowAny()

View File

@ -1,121 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db import transaction
from rest_framework import viewsets
from rest_framework import status
from rest_framework import mixins
from rest_framework.response import Response
from . import pagination
from . import serializers
# Transactional version of rest framework mixins.
class CreateModelMixin(mixins.CreateModelMixin):
@transaction.atomic
def create(self, *args, **kwargs):
return super().create(*args, **kwargs)
class RetrieveModelMixin(mixins.RetrieveModelMixin):
@transaction.atomic
def retrieve(self, *args, **kwargs):
return super().retrieve(*args, **kwargs)
class UpdateModelMixin(mixins.UpdateModelMixin):
@transaction.atomic
def update(self, *args, **kwargs):
return super().update(*args, **kwargs)
@transaction.atomic
def partial_update(self, request, *args, **kwargs):
return super().partial_update(request, *args, **kwargs)
class ListModelMixin(mixins.ListModelMixin):
@transaction.atomic
def list(self, *args, **kwargs):
return super().list(*args, **kwargs)
class DestroyModelMixin(mixins.DestroyModelMixin):
@transaction.atomic
def destroy(self, request, *args, **kwargs):
return super().destroy(request, *args, **kwargs)
# Other mixins (what they are doing here?)
class PreconditionMixin(object):
def pre_conditions_on_save(self, obj):
pass
def pre_conditions_on_delete(self, obj):
pass
def pre_save(self, obj):
super().pre_save(obj)
self.pre_conditions_on_save(obj)
def pre_delete(self, obj):
super().pre_delete(obj)
self.pre_conditions_on_delete(obj)
class DetailAndListSerializersMixin(object):
"""
Use a diferent serializer class to the list action.
"""
list_serializer_class = None
def get_serializer_class(self):
if self.action == "list" and self.list_serializer_class:
return self.list_serializer_class
return super().get_serializer_class()
# Own subclasses of django rest framework viewsets
class ModelCrudViewSet(DetailAndListSerializersMixin,
PreconditionMixin,
pagination.HeadersPaginationMixin,
pagination.ConditionalPaginationMixin,
CreateModelMixin,
RetrieveModelMixin,
UpdateModelMixin,
DestroyModelMixin,
ListModelMixin,
viewsets.GenericViewSet):
pass
class ModelListViewSet(DetailAndListSerializersMixin,
PreconditionMixin,
pagination.HeadersPaginationMixin,
pagination.ConditionalPaginationMixin,
RetrieveModelMixin,
ListModelMixin,
viewsets.GenericViewSet):
pass
class GenericViewSet(viewsets.GenericViewSet):
pass

View File

@ -0,0 +1,26 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This code is partially taken from django-rest-framework:
# Copyright (c) 2011-2014, Tom Christie
from .viewsets import ModelListViewSet
from .viewsets import ModelCrudViewSet
from .viewsets import GenericViewSet
__all__ = ["ModelCrudViewSet",
"ModelListViewSet",
"GenericViewSet"]

493
taiga/base/api/generics.py Normal file
View File

@ -0,0 +1,493 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This code is partially taken from django-rest-framework:
# Copyright (c) 2011-2014, Tom Christie
import warnings
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.core.paginator import Paginator, InvalidPage
from django.http import Http404
from django.shortcuts import get_object_or_404 as _get_object_or_404
from django.utils.translation import ugettext as _
from rest_framework import exceptions
from rest_framework.request import clone_request
from rest_framework.settings import api_settings
from . import views
from . import mixins
def strict_positive_int(integer_string, cutoff=None):
"""
Cast a string to a strictly positive integer.
"""
ret = int(integer_string)
if ret <= 0:
raise ValueError()
if cutoff:
ret = min(ret, cutoff)
return ret
def get_object_or_404(queryset, *filter_args, **filter_kwargs):
"""
Same as Django's standard shortcut, but make sure to raise 404
if the filter_kwargs don't match the required types.
"""
try:
return _get_object_or_404(queryset, *filter_args, **filter_kwargs)
except (TypeError, ValueError):
raise Http404
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
# You'll need to either set these attributes,
# or override `get_queryset()`/`get_serializer_class()`.
queryset = None
serializer_class = None
# This shortcut may be used instead of setting either or both
# of the `queryset`/`serializer_class` attributes, although using
# the explicit style is generally preferred.
model = None
# If you want to use object lookups other than pk, set this attribute.
# For more complex lookup requirements override `get_object()`.
lookup_field = 'pk'
lookup_url_kwarg = None
# Pagination settings
paginate_by = api_settings.PAGINATE_BY
paginate_by_param = api_settings.PAGINATE_BY_PARAM
max_paginate_by = api_settings.MAX_PAGINATE_BY
pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
page_kwarg = 'page'
# The filter backend classes to use for queryset filtering
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
# The following attributes may be subject to change,
# and should be considered private API.
model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS
paginator_class = Paginator
######################################
# These are pending deprecation...
pk_url_kwarg = 'pk'
slug_url_kwarg = 'slug'
slug_field = 'slug'
allow_empty = True
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
def get_serializer(self, instance=None, data=None,
files=None, many=False, partial=False):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(instance, data=data, files=files,
many=many, partial=partial, context=context)
def get_pagination_serializer(self, page):
"""
Return a serializer instance to use with paginated data.
"""
class SerializerClass(self.pagination_serializer_class):
class Meta:
object_serializer_class = self.get_serializer_class()
pagination_serializer_class = SerializerClass
context = self.get_serializer_context()
return pagination_serializer_class(instance=page, context=context)
def paginate_queryset(self, queryset, page_size=None):
"""
Paginate a queryset if required, either returning a page object,
or `None` if pagination is not configured for this view.
"""
deprecated_style = False
if page_size is not None:
warnings.warn('The `page_size` parameter to `paginate_queryset()` '
'is due to be deprecated. '
'Note that the return style of this method is also '
'changed, and will simply return a page object '
'when called without a `page_size` argument.',
PendingDeprecationWarning, stacklevel=2)
deprecated_style = True
else:
# Determine the required page size.
# If pagination is not configured, simply return None.
page_size = self.get_paginate_by()
if not page_size:
return None
if not self.allow_empty:
warnings.warn(
'The `allow_empty` parameter is due to be deprecated. '
'To use `allow_empty=False` style behavior, You should override '
'`get_queryset()` and explicitly raise a 404 on empty querysets.',
PendingDeprecationWarning, stacklevel=2
)
paginator = self.paginator_class(queryset, page_size,
allow_empty_first_page=self.allow_empty)
page_kwarg = self.kwargs.get(self.page_kwarg)
page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg)
page = page_kwarg or page_query_param or 1
try:
page_number = paginator.validate_number(page)
except InvalidPage:
if page == 'last':
page_number = paginator.num_pages
else:
raise Http404(_("Page is not 'last', nor can it be converted to an int."))
try:
page = paginator.page(page_number)
except InvalidPage as e:
raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
'page_number': page_number,
'message': str(e)
})
if deprecated_style:
return (paginator, page, page.object_list, page.has_other_pages())
return page
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in self.get_filter_backends():
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def get_filter_backends(self):
"""
Returns the list of filter backends that this view requires.
"""
filter_backends = self.filter_backends or []
if not filter_backends and hasattr(self, 'filter_backend'):
raise RuntimeException('The `filter_backend` attribute and `FILTER_BACKEND` setting '
'are due to be deprecated in favor of a `filter_backends` '
'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take '
'a *list* of filter backend classes.')
return filter_backends
########################
### The following methods provide default implementations
### that you may want to override for more complex cases.
def get_paginate_by(self, queryset=None):
"""
Return the size of pages to use with pagination.
If `PAGINATE_BY_PARAM` is set it will attempt to get the page size
from a named query parameter in the url, eg. ?page_size=100
Otherwise defaults to using `self.paginate_by`.
"""
if queryset is not None:
raise RuntimeException('The `queryset` parameter to `get_paginate_by()` '
'is due to be deprecated.')
if self.paginate_by_param:
try:
return strict_positive_int(
self.request.QUERY_PARAMS[self.paginate_by_param],
cutoff=self.max_paginate_by
)
except (KeyError, ValueError):
pass
return self.paginate_by
def get_serializer_class(self):
if self.action == "list" and hasattr(self, "list_serializer_class"):
return self.list_serializer_class
serializer_class = self.serializer_class
if serializer_class is not None:
return serializer_class
assert self.model is not None, \
"'%s' should either include a 'serializer_class' attribute, " \
"or use the 'model' attribute as a shortcut for " \
"automatically generating a serializer class." \
% self.__class__.__name__
class DefaultSerializer(self.model_serializer_class):
class Meta:
model = self.model
return DefaultSerializer
def get_queryset(self):
"""
Get the list of items for this view.
This must be an iterable, and may be a queryset.
Defaults to using `self.queryset`.
You may want to override this if you need to provide different
querysets depending on the incoming request.
(Eg. return a list of items that is specific to the user)
"""
if self.queryset is not None:
return self.queryset._clone()
if self.model is not None:
return self.model._default_manager.all()
raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'"
% self.__class__.__name__)
def get_object(self, queryset=None):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
# Determine the base queryset to use.
if queryset is None:
queryset = self.filter_queryset(self.get_queryset())
else:
# NOTE: explicit exception for avoid and fix
# usage of deprecated way of get_object
raise RuntimeException("DEPRECATED")
# Perform the lookup filtering.
# Note that `pk` and `slug` are deprecated styles of lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup = self.kwargs.get(lookup_url_kwarg, None)
pk = self.kwargs.get(self.pk_url_kwarg, None)
slug = self.kwargs.get(self.slug_url_kwarg, None)
if lookup is not None:
filter_kwargs = {self.lookup_field: lookup}
elif pk is not None and self.lookup_field == 'pk':
raise RuntimeException('The `pk_url_kwarg` attribute is due to be deprecated. '
'Use the `lookup_field` attribute instead')
elif slug is not None and self.lookup_field == 'pk':
raise RuntimeException('The `slug_url_kwarg` attribute is due to be deprecated. '
'Use the `lookup_field` attribute instead')
else:
raise ImproperlyConfigured(
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, self.lookup_field)
)
obj = get_object_or_404(queryset, **filter_kwargs)
return obj
def get_object_or_none(self):
try:
return self.get_object()
except Http404:
return None
########################
### The following are placeholder methods,
### and are intended to be overridden.
###
### The are not called by GenericAPIView directly,
### but are used by the mixin methods.
def pre_conditions_on_save(self, obj):
"""
Placeholder method called by mixins before save for check
some conditions before save.
"""
pass
def pre_conditions_on_delete(self, obj):
"""
Placeholder method called by mixins before delete for check
some conditions before delete.
"""
pass
def pre_save(self, obj):
"""
Placeholder method for calling before saving an object.
May be used to set attributes on the object that are implicit
in either the request, or the url.
"""
pass
def post_save(self, obj, created=False):
"""
Placeholder method for calling after saving an object.
"""
pass
def pre_delete(self, obj):
"""
Placeholder method for calling before deleting an object.
"""
pass
def post_delete(self, obj):
"""
Placeholder method for calling after deleting an object.
"""
pass
##########################################################
### Concrete view classes that provide method handlers ###
### by composing the mixin classes with the base view. ###
### NOTE: not used by taiga. ###
##########################################################
class CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for creating a model instance.
"""
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
class RetrieveAPIView(mixins.RetrieveModelMixin,
GenericAPIView):
"""
Concrete view for retrieving a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
class DestroyAPIView(mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for deleting a model instance.
"""
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class UpdateAPIView(mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for updating a model instance.
"""
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)

217
taiga/base/api/mixins.py Normal file
View File

@ -0,0 +1,217 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This code is partially taken from django-rest-framework:
# Copyright (c) 2011-2014, Tom Christie
import warnings
from django.core.exceptions import ValidationError
from django.http import Http404
from django.db import transaction as tx
from rest_framework import status
from rest_framework.response import Response
from rest_framework.request import clone_request
from rest_framework.settings import api_settings
def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None):
"""
Given a model instance, and an optional pk and slug field,
return the full list of all other field names on that model.
For use when performing full_clean on a model instance,
so we only clean the required fields.
"""
include = []
if pk:
# Pending deprecation
pk_field = obj._meta.pk
while pk_field.rel:
pk_field = pk_field.rel.to._meta.pk
include.append(pk_field.name)
if slug_field:
# Pending deprecation
include.append(slug_field)
if lookup_field and lookup_field != 'pk':
include.append(lookup_field)
return [field.name for field in obj._meta.fields if field.name not in include]
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
self.check_permissions(request, 'create', serializer.object)
self.pre_save(serializer.object)
self.pre_conditions_on_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def get_success_headers(self, data):
try:
return {'Location': data[api_settings.URL_FIELD_NAME]}
except (TypeError, KeyError):
return {}
class ListModelMixin(object):
"""
List a queryset.
"""
empty_error = "Empty list and '%(class_name)s.allow_empty' is False."
def list(self, request, *args, **kwargs):
self.object_list = self.filter_queryset(self.get_queryset())
# Default is to allow empty querysets. This can be altered by setting
# `.allow_empty = False`, to raise 404 errors on empty querysets.
if not self.allow_empty and not self.object_list:
warnings.warn(
'The `allow_empty` parameter is due to be deprecated. '
'To use `allow_empty=False` style behavior, You should override '
'`get_queryset()` and explicitly raise a 404 on empty querysets.',
PendingDeprecationWarning
)
class_name = self.__class__.__name__
error_msg = self.empty_error % {'class_name': class_name}
raise Http404(error_msg)
# Switch between paginated or standard style responses
page = self.paginate_queryset(self.object_list)
if page is not None:
serializer = self.get_pagination_serializer(page)
else:
serializer = self.get_serializer(self.object_list, many=True)
return Response(serializer.data)
class RetrieveModelMixin(object):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
self.object = self.get_object_or_none()
self.check_permissions(request, 'retrieve', self.object)
if self.object is None:
raise Http404
serializer = self.get_serializer(self.object)
return Response(serializer.data)
class UpdateModelMixin(object):
"""
Update a model instance.
"""
@tx.atomic
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
self.object = self.get_object_or_none()
self.check_permissions(request, 'update', self.object)
serializer = self.get_serializer(self.object, data=request.DATA,
files=request.FILES, partial=partial)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# Hooks
try:
self.pre_save(serializer.object)
self.pre_conditions_on_save(serializer.object)
except ValidationError as err:
# full_clean on model instance may be called in pre_save,
# so we have to handle eventual errors.
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
if self.object is None:
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
self.object = serializer.save(force_update=True)
self.post_save(self.object, created=False)
return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def pre_save(self, obj):
"""
Set any attributes on the object that are implicit in the request.
"""
# pk and/or slug attributes are implicit in the URL.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup = self.kwargs.get(lookup_url_kwarg, None)
pk = self.kwargs.get(self.pk_url_kwarg, None)
slug = self.kwargs.get(self.slug_url_kwarg, None)
slug_field = slug and self.slug_field or None
if lookup:
setattr(obj, self.lookup_field, lookup)
if pk:
setattr(obj, 'pk', pk)
if slug:
setattr(obj, slug_field, slug)
# Ensure we clean the attributes so that we don't eg return integer
# pk using a string representation, as provided by the url conf kwarg.
if hasattr(obj, 'full_clean'):
exclude = _get_validation_exclusions(obj, pk, slug_field, self.lookup_field)
obj.full_clean(exclude)
class DestroyModelMixin(object):
"""
Destroy a model instance.
"""
@tx.atomic
def destroy(self, request, *args, **kwargs):
obj = self.get_object_or_none()
self.check_permissions(request, 'destroy', obj)
if obj is None:
raise Http404
self.pre_delete(obj)
self.pre_conditions_on_delete(obj)
obj.delete()
self.post_delete(obj)
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@ -0,0 +1,218 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import abc
from taiga.base.utils import sequence as sq
from taiga.permissions.service import user_has_perm, is_project_owner
from django.db.models.loading import get_model
######################################################################
# Base permissiones definition
######################################################################
class ResourcePermission(object):
"""
Base class for define resource permissions.
"""
enought_perms = None
global_perms = None
retrieve_perms = None
create_perms = None
update_perms = None
destroy_perms = None
list_perms = None
def __init__(self, request, view):
self.request = request
self.view = view
def check_permissions(self, action:str, obj:object=None):
permset = getattr(self, "{}_perms".format(action))
if isinstance(permset, (list, tuple)):
permset = reduce(lambda acc, v: acc & v, permset)
elif permset is None:
# Use empty operator that always return true with
# empty components.
permset = And()
elif isinstance(permset, PermissionComponent):
# Do nothing
pass
elif inspect.isclass(permset) and issubclass(permset, PermissionComponent):
permset = permset()
else:
raise RuntimeError("Invalid permission definition.")
if self.global_perms:
permset = (self.global_perms & permset)
if self.enought_perms:
permset = (self.enought_perms | permset)
return permset.check_permissions(request=self.request,
view=self.view,
obj=obj)
class PermissionComponent(object, metaclass=abc.ABCMeta):
@abc.abstractmethod
def check_permissions(self, request, view, obj=None):
pass
def __invert__(self):
return Not(self)
def __and__(self, component):
return And(self, component)
def __or__(self, component):
return Or(self, component)
class PermissionOperator(PermissionComponent):
"""
Base class for all logical operators for compose
components.
"""
def __init__(self, *components):
self.components = tuple(components)
class Not(PermissionOperator):
"""
Negation operator as permission composable component.
"""
# Overwrites the default constructor for fix
# to one parameter instead of variable list of them.
def __init__(self, component):
super().__init__(component)
def check_permissions(self, *args, **kwargs):
component = sq.first(self.components)
return (not component.check_permissions(*args, **kwargs))
class Or(PermissionOperator):
"""
Or logical operator as permission component.
"""
def check_permissions(self, *args, **kwargs):
valid = False
for component in self.components:
if component.check_permissions(*args, **kwargs):
valid = True
break
return valid
class And(PermissionOperator):
"""
And logical operator as permission component.
"""
def check_permissions(self, *args, **kwargs):
valid = True
for component in self.components:
if not component.check_permissions(*args, **kwargs):
valid = False
break
return valid
######################################################################
# Generic components.
######################################################################
class AllowAny(PermissionComponent):
def check_permissions(self, request, view, obj=None):
return True
class DenyAll(PermissionComponent):
def check_permissions(self, request, view, obj=None):
return False
class IsAuthenticated(PermissionComponent):
def check_permissions(self, request, view, obj=None):
return request.user and request.user.is_authenticated()
class IsSuperUser(PermissionComponent):
def check_permissions(self, request, view, obj=None):
return request.user and request.user.is_authenticated() and request.user.is_superuser
class HasProjectPerm(PermissionComponent):
def __init__(self, perm, *components):
self.project_perm = perm
super().__init__(*components)
def check_permissions(self, request, view, obj=None):
return user_has_perm(request.user, self.project_perm, obj)
class HasProjectParamAndPerm(PermissionComponent):
def __init__(self, perm, *components):
self.project_perm = perm
super().__init__(*components)
def check_permissions(self, request, view, obj=None):
Project = get_model('projects', 'Project')
project_id = request.QUERY_PARAMS.get("project", None)
try:
project = Project.objects.get(pk=project_id)
except Project.DoesNotExist:
return False
return user_has_perm(request.user, self.project_perm, project)
class HasMandatoryParam(PermissionComponent):
def __init__(self, param, *components):
self.mandatory_param = param
super().__init__(*components)
def check_permissions(self, request, view, obj=None):
param = request.GET.get(self.mandatory_param, None)
if param:
return True
return False
class IsProjectOwner(PermissionComponent):
def check_permissions(self, request, view, obj=None):
return is_project_owner(request.user, obj)
######################################################################
# Generic permissions.
######################################################################
class AllowAnyPermission(ResourcePermission):
enought_perms = AllowAny()
class IsAuthenticatedPermission(ResourcePermission):
enought_perms = IsAuthenticated()

432
taiga/base/api/views.py Normal file
View File

@ -0,0 +1,432 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This code is partially taken from django-rest-framework:
# Copyright (c) 2011-2014, Tom Christie
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.utils.datastructures import SortedDict
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions
from rest_framework.compat import smart_text, HttpResponseBase, View
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.utils import formatting
from taiga.base.utils.iterators import as_tuple
def get_view_name(view_cls, suffix=None):
"""
Given a view class, return a textual name to represent the view.
This name is used in the browsable API, and in OPTIONS responses.
This function is the default for the `VIEW_NAME_FUNCTION` setting.
"""
name = view_cls.__name__
name = formatting.remove_trailing_string(name, 'View')
name = formatting.remove_trailing_string(name, 'ViewSet')
name = formatting.camelcase_to_spaces(name)
if suffix:
name += ' ' + suffix
return name
def get_view_description(view_cls, html=False):
"""
Given a view class, return a textual description to represent the view.
This name is used in the browsable API, and in OPTIONS responses.
This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting.
"""
description = view_cls.__doc__ or ''
description = formatting.dedent(smart_text(description))
if html:
return formatting.markup_description(description)
return description
def exception_handler(exc):
"""
Returns the response that should be used for any given exception.
By default we handle the REST framework `APIException`, and also
Django's builtin `Http404` and `PermissionDenied` exceptions.
Any unhandled exceptions may return `None`, which will cause a 500 error
to be raised.
"""
if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait
return Response({'detail': exc.detail},
status=exc.status_code,
headers=headers)
elif isinstance(exc, Http404):
return Response({'detail': 'Not found'},
status=status.HTTP_404_NOT_FOUND)
elif isinstance(exc, PermissionDenied):
return Response({'detail': 'Permission denied'},
status=status.HTTP_403_FORBIDDEN)
# Note: Unhandled exceptions will raise a 500 error.
return None
class APIView(View):
# The following policies may be set at either globally, or per-view.
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
# Allow dependancy injection of other settings to make testing easier.
settings = api_settings
@classmethod
def as_view(cls, **initkwargs):
"""
Store the original class on the view function.
This allows us to discover information about the view when we do URL
reverse lookups. Used for breadcrumb generation.
"""
view = super(APIView, cls).as_view(**initkwargs)
view.cls = cls
return view
@property
def allowed_methods(self):
"""
Wrap Django's private `_allowed_methods` interface in a public property.
"""
return self._allowed_methods()
@property
def default_response_headers(self):
headers = {
'Allow': ', '.join(self.allowed_methods),
}
if len(self.renderer_classes) > 1:
headers['Vary'] = 'Accept'
return headers
def http_method_not_allowed(self, request, *args, **kwargs):
"""
If `request.method` does not correspond to a handler method,
determine what kind of exception to raise.
"""
raise exceptions.MethodNotAllowed(request.method)
def permission_denied(self, request):
"""
If request is not permitted, determine what kind of exception to raise.
"""
if not request.successful_authenticator:
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied()
def throttled(self, request, wait):
"""
If request is throttled, determine what kind of exception to raise.
"""
raise exceptions.Throttled(wait)
def get_authenticate_header(self, request):
"""
If a request is unauthenticated, determine the WWW-Authenticate
header to use for 401 responses, if any.
"""
authenticators = self.get_authenticators()
if authenticators:
return authenticators[0].authenticate_header(request)
def get_parser_context(self, http_request):
"""
Returns a dict that is passed through to Parser.parse(),
as the `parser_context` keyword argument.
"""
# Note: Additionally `request` and `encoding` will also be added
# to the context by the Request object.
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {})
}
def get_renderer_context(self):
"""
Returns a dict that is passed through to Renderer.render(),
as the `renderer_context` keyword argument.
"""
# Note: Additionally 'response' will also be added to the context,
# by the Response object.
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {}),
'request': getattr(self, 'request', None)
}
def get_view_name(self):
"""
Return the view name, as used in OPTIONS responses and in the
browsable API.
"""
func = self.settings.VIEW_NAME_FUNCTION
return func(self.__class__, getattr(self, 'suffix', None))
def get_view_description(self, html=False):
"""
Return some descriptive text for the view, as used in OPTIONS responses
and in the browsable API.
"""
func = self.settings.VIEW_DESCRIPTION_FUNCTION
return func(self.__class__, html)
# API policy instantiation methods
def get_format_suffix(self, **kwargs):
"""
Determine if the request includes a '.json' style format suffix
"""
if self.settings.FORMAT_SUFFIX_KWARG:
return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG)
def get_renderers(self):
"""
Instantiates and returns the list of renderers that this view can use.
"""
return [renderer() for renderer in self.renderer_classes]
def get_parsers(self):
"""
Instantiates and returns the list of parsers that this view can use.
"""
return [parser() for parser in self.parser_classes]
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
return [auth() for auth in self.authentication_classes]
@as_tuple
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
for permcls in self.permission_classes:
instance = permcls(request=self.request,
view=self)
yield instance
def get_throttles(self):
"""
Instantiates and returns the list of throttles that this view uses.
"""
return [throttle() for throttle in self.throttle_classes]
def get_content_negotiator(self):
"""
Instantiate and return the content negotiation class to use.
"""
if not getattr(self, '_negotiator', None):
self._negotiator = self.content_negotiation_class()
return self._negotiator
# API policy implementation methods
def perform_content_negotiation(self, request, force=False):
"""
Determine which renderer and media type to use render the response.
"""
renderers = self.get_renderers()
conneg = self.get_content_negotiator()
try:
return conneg.select_renderer(request, renderers, self.format_kwarg)
except Exception:
if force:
return (renderers[0], renderers[0].media_type)
raise
def perform_authentication(self, request):
"""
Perform authentication on the incoming request.
Note that if you override this and simply 'pass', then authentication
will instead be performed lazily, the first time either
`request.user` or `request.auth` is accessed.
"""
request.user
def check_permissions(self, request, action, obj=None):
for permission in self.get_permissions():
if not permission.check_permissions(action=action, obj=obj):
self.permission_denied(request)
def check_throttles(self, request):
"""
Check if request should be throttled.
Raises an appropriate exception if the request is throttled.
"""
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
self.throttled(request, throttle.wait())
# Dispatch methods
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request(request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context)
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)
# Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_throttles(request)
# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
def finalize_response(self, request, response, *args, **kwargs):
"""
Returns the final response object.
"""
# Make the error obvious if a proper response is not returned
assert isinstance(response, HttpResponseBase), (
'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
'to be returned from the view, but received a `%s`'
% type(response)
)
if isinstance(response, Response):
if not getattr(request, 'accepted_renderer', None):
neg = self.perform_content_negotiation(request, force=True)
request.accepted_renderer, request.accepted_media_type = neg
response.accepted_renderer = request.accepted_renderer
response.accepted_media_type = request.accepted_media_type
response.renderer_context = self.get_renderer_context()
for key, value in self.headers.items():
response[key] = value
return response
def handle_exception(self, exc):
"""
Handle any exception that occurs, by returning an appropriate response,
or re-raising the error.
"""
if isinstance(exc, (exceptions.NotAuthenticated,
exceptions.AuthenticationFailed)):
# WWW-Authenticate header for 401 responses, else coerce to 403
auth_header = self.get_authenticate_header(self.request)
if auth_header:
exc.auth_header = auth_header
else:
exc.status_code = status.HTTP_403_FORBIDDEN
response = self.settings.EXCEPTION_HANDLER(exc)
if response is None:
raise
response.exception = True
return response
# Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt.
@csrf_exempt
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
def options(self, request, *args, **kwargs):
"""
Handler method for HTTP 'OPTIONS' request.
We may as well implement this as Django will otherwise provide
a less useful default implementation.
"""
return Response(self.metadata(request), status=status.HTTP_200_OK)
def metadata(self, request):
"""
Return a dictionary of metadata about the view.
Used to return responses for OPTIONS requests.
"""
# By default we can't provide any form-like information, however the
# generic views override this implementation and add additional
# information for POST and PUT methods, based on the serializer.
ret = SortedDict()
ret['name'] = self.get_view_name()
ret['description'] = self.get_view_description()
ret['renders'] = [renderer.media_type for renderer in self.renderer_classes]
ret['parses'] = [parser.media_type for parser in self.parser_classes]
return ret

161
taiga/base/api/viewsets.py Normal file
View File

@ -0,0 +1,161 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This code is partially taken from django-rest-framework:
# Copyright (c) 2011-2014, Tom Christie
from functools import update_wrapper
from django.utils.decorators import classonlymethod
from . import views
from . import mixins
from . import generics
from . import pagination
class ViewSetMixin(object):
"""
This is the magic.
Overrides `.as_view()` so that it takes an `actions` keyword that performs
the binding of HTTP methods to actions on the Resource.
For example, to create a concrete view binding the 'GET' and 'POST' methods
to the 'list' and 'create' actions...
view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
"""
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
# The suffix initkwarg is reserved for identifing the viewset type
# eg. 'List' or 'Instance'.
cls.suffix = None
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r" % (
cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
# We also store the mapping of request methods to actions,
# so that we can later set the action attribute.
# eg. `self.action = 'list'` on an incoming GET request.
self.action_map = actions
# Bind methods to actions
# This is the bit that's different to a standard view
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
# Patch this in as it's otherwise only present from 1.5 onwards
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
# And continue as usual
return self.dispatch(request, *args, **kwargs)
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
# We need to set these on the view function, so that breadcrumb
# generation can pick out these bits of information from a
# resolved URL.
view.cls = cls
view.suffix = initkwargs.get('suffix', None)
return view
def initialize_request(self, request, *args, **kargs):
"""
Set the `.action` attribute on the view,
depending on the request method.
"""
request = super(ViewSetMixin, self).initialize_request(request, *args, **kargs)
self.action = self.action_map.get(request.method.lower())
return request
def check_permissions(self, request, action:str=None, obj:object=None):
if action is None:
action = self.action
return super().check_permissions(request, action=action, obj=obj)
class ViewSet(ViewSetMixin, views.APIView):
"""
The base ViewSet class does not provide any actions by default.
"""
pass
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `list()` and `retrieve()` actions.
"""
pass
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
class ModelCrudViewSet(pagination.HeadersPaginationMixin,
pagination.ConditionalPaginationMixin,
ModelViewSet):
pass
class ModelListViewSet(pagination.HeadersPaginationMixin,
pagination.ConditionalPaginationMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
pass

View File

@ -26,6 +26,7 @@ def detail_route(methods=['get'], **kwargs):
def decorator(func): def decorator(func):
func.bind_to_methods = methods func.bind_to_methods = methods
func.detail = True func.detail = True
func.permission_classes = kwargs.get('permission_classes', [])
func.kwargs = kwargs func.kwargs = kwargs
return func return func
return decorator return decorator
@ -38,6 +39,7 @@ def list_route(methods=['get'], **kwargs):
def decorator(func): def decorator(func):
func.bind_to_methods = methods func.bind_to_methods = methods
func.detail = False func.detail = False
func.permission_classes = kwargs.get('permission_classes', [])
func.kwargs = kwargs func.kwargs = kwargs
return func return func
return decorator return decorator
@ -52,6 +54,7 @@ def link(**kwargs):
def decorator(func): def decorator(func):
func.bind_to_methods = ['get'] func.bind_to_methods = ['get']
func.detail = True func.detail = True
func.permission_classes = kwargs.get('permission_classes', [])
func.kwargs = kwargs func.kwargs = kwargs
return func return func
return decorator return decorator
@ -66,6 +69,7 @@ def action(methods=['post'], **kwargs):
def decorator(func): def decorator(func):
func.bind_to_methods = methods func.bind_to_methods = methods
func.detail = True func.detail = True
func.permission_classes = kwargs.get('permission_classes', [])
func.kwargs = kwargs func.kwargs = kwargs
return func return func
return decorator return decorator

View File

@ -16,13 +16,14 @@
from django.db.models import Q from django.db.models import Q
from django.db.models.sql.where import ExtraWhere, OR
from rest_framework import filters from rest_framework import filters
from taiga.base import tags from taiga.base import tags
class QueryParamsFilterMixin(object): class QueryParamsFilterMixin(filters.BaseFilterBackend):
_special_values_dict = { _special_values_dict = {
'true': True, 'true': True,
'false': False, 'false': False,
@ -53,7 +54,8 @@ class QueryParamsFilterMixin(object):
return queryset return queryset
class OrderByFilterMixin(object):
class OrderByFilterMixin(QueryParamsFilterMixin):
order_by_query_param = "order_by" order_by_query_param = "order_by"
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
@ -72,18 +74,122 @@ class OrderByFilterMixin(object):
if field_name not in order_by_fields: if field_name not in order_by_fields:
return queryset return queryset
return queryset.order_by(raw_fieldname) return super().filter_queryset(request, queryset.order_by(raw_fieldname), view)
class FilterBackend(OrderByFilterMixin, class FilterBackend(OrderByFilterMixin):
QueryParamsFilterMixin,
filters.BaseFilterBackend):
""" """
Default filter backend. Default filter backend.
""" """
pass pass
class PermissionBasedFilterBackend(FilterBackend):
permission = None
def filter_queryset(self, request, queryset, view):
# TODO: Make permissions aware of members permissions, now only check membership.
qs = queryset
if request.user.is_authenticated() and request.user.is_superuser:
qs = qs
elif request.user.is_authenticated():
qs = qs.filter(Q(project__owner=request.user) |
Q(project__members=request.user) |
Q(project__is_private=False))
qs.query.where.add(ExtraWhere(["projects_project.public_permissions @> ARRAY['{}']".format(self.permission)], []), OR)
else:
qs = qs.filter(project__is_private=False)
qs.query.where.add(ExtraWhere(["projects_project.anon_permissions @> ARRAY['{}']".format(self.permission)], []), OR)
return super().filter_queryset(request, qs.distinct(), view)
class CanViewProjectFilterBackend(PermissionBasedFilterBackend):
permission = "view_project"
class CanViewUsFilterBackend(PermissionBasedFilterBackend):
permission = "view_us"
class CanViewIssuesFilterBackend(PermissionBasedFilterBackend):
permission = "view_issues"
class CanViewTasksFilterBackend(PermissionBasedFilterBackend):
permission = "view_tasks"
class CanViewWikiPagesFilterBackend(PermissionBasedFilterBackend):
permission = "view_wiki_pages"
class CanViewWikiLinksFilterBackend(PermissionBasedFilterBackend):
permission = "view_wiki_links"
class CanViewMilestonesFilterBackend(PermissionBasedFilterBackend):
permission = "view_milestones"
class PermissionBasedAttachmentFilterBackend(FilterBackend):
permission = None
def filter_queryset(self, request, queryset, view):
# TODO: Make permissions aware of members permissions, now only check membership.
qs = queryset
if request.user.is_authenticated() and request.user.is_superuser:
qs = qs
elif request.user.is_authenticated():
qs = qs.filter(Q(project__owner=request.user) |
Q(project__members=request.user) |
Q(project__is_private=False))
qs.query.where.add(ExtraWhere(["projects_project.public_permissions @> ARRAY['{}']".format(self.permission)], []), OR)
else:
qs = qs.filter(project__is_private=False)
qs.query.where.add(ExtraWhere(["projects_project.anon_permissions @> ARRAY['{}']".format(self.permission)], []), OR)
ct = view.get_content_type()
qs = qs.filter(content_type=ct)
return super().filter_queryset(request, qs.distinct(), view)
class CanViewUserStoryAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
permission = "view_us"
class CanViewTaskAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
permission = "view_tasks"
class CanViewIssueAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
permission = "view_issues"
class CanViewWikiAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
permission = "view_wiki_pages"
class CanViewProjectObjFilterBackend(FilterBackend):
def filter_queryset(self, request, queryset, view):
qs = queryset
if request.user.is_authenticated() and request.user.is_superuser:
qs = qs
elif request.user.is_authenticated():
qs = qs.filter(Q(owner=request.user) |
Q(members=request.user) |
Q(is_private=False))
qs.query.where.add(ExtraWhere(["projects_project.public_permissions @> ARRAY['view_project']"], []), OR)
else:
qs = qs.filter(is_private=False)
qs.query.where.add(ExtraWhere(["projects_project.anon_permissions @> ARRAY['view_project']"], []), OR)
return super().filter_queryset(request, qs.distinct(), view)
class IsProjectMemberFilterBackend(FilterBackend): class IsProjectMemberFilterBackend(FilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
queryset = super().filter_queryset(request, queryset, view) queryset = super().filter_queryset(request, queryset, view)
@ -92,7 +198,7 @@ class IsProjectMemberFilterBackend(FilterBackend):
if user.is_authenticated(): if user.is_authenticated():
queryset = queryset.filter(Q(project__members=request.user) | queryset = queryset.filter(Q(project__members=request.user) |
Q(project__owner=request.user)) Q(project__owner=request.user))
return queryset.distinct() return super().filter_queryset(request, queryset.distinct(), view)
class TagsFilter(FilterBackend): class TagsFilter(FilterBackend):
@ -107,4 +213,4 @@ class TagsFilter(FilterBackend):
if query_tags: if query_tags:
queryset = tags.filter(queryset, contains=query_tags) queryset = tags.filter(queryset, contains=query_tags)
return queryset return super().filter_queryset(request, queryset, view)

View File

@ -45,6 +45,19 @@ class JsonField(serializers.WritableField):
return data return data
class PgArrayField(serializers.WritableField):
"""
PgArray objects serializer.
"""
widget = widgets.Textarea
def to_native(self, obj):
return obj
def from_native(self, data):
return data
class NeighborsSerializerMixin: class NeighborsSerializerMixin:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -0,0 +1,25 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def first(iterable):
if len(iterable) == 0:
return None
return iterable[0]
def next(data:list):
return data[1:]

View File

View File

View File

@ -0,0 +1,80 @@
from django.utils.translation import ugettext_lazy as _
ANON_PERMISSIONS = [
('view_project', _('View project')),
('view_milestones', _('View milestones')),
('view_us', _('View user stories')),
('view_tasks', _('View tasks')),
('view_issues', _('View issues')),
('view_wiki_pages', _('View wiki pages')),
('view_wiki_links', _('View wiki links')),
]
USER_PERMISSIONS = [
('view_project', _('View project')),
('view_milestones', _('View milestones')),
('view_us', _('View user stories')),
('view_issues', _('View issues')),
('vote_issues', _('Vote issues')),
('view_tasks', _('View tasks')),
('view_wiki_pages', _('View wiki pages')),
('view_wiki_links', _('View wiki links')),
('request_membership', _('Request membership')),
('add_us_to_project', _('Add user story to project')),
('add_comments_to_us', _('Add comments to user stories')),
('add_comments_to_task', _('Add comments to tasks')),
('add_issue', _('Add issues')),
('add_comments_issue', _('Add comments to issues')),
('add_wiki_page', _('Add wiki page')),
('modify_wiki_page', _('Modify wiki page')),
('add_wiki_link', _('Add wiki link')),
('modify_wiki_link', _('Modify wiki link')),
]
MEMBERS_PERMISSIONS = [
('view_project', _('View project')),
# Milestone permissions
('view_milestones', _('View milestones')),
('add_milestone', _('Add milestone')),
('modify_milestone', _('Modify milestone')),
('delete_last_milestone', _('Delete last milestone')),
('delete_milestone', _('Delete milestone')),
('add_us_to_milestone', _('Add use to milestone')),
('remove_us_from_milestone', _('Remove us from milestone')),
('reorder_us_on_milestone', _('Reorder us on milestone')),
# US permissions
('view_us', _('View user story')),
('add_us', _('Add user story')),
('modify_us', _('Modify user story')),
('delete_us', _('Delete user story')),
# Task permissions
('view_tasks', _('View tasks')),
('add_task', _('Add task')),
('modify_task', _('Modify task')),
('delete_task', _('Delete task')),
# Issue permissions
('view_issues', _('View issues')),
('vote_issues', _('Vote issues')),
('add_issue', _('Add issue')),
('modify_issue', _('Modify issue')),
('delete_issue', _('Delete issue')),
# Wiki page permissions
('view_wiki_pages', _('View wiki pages')),
('add_wiki_page', _('Add wiki page')),
('modify_wiki_page', _('Modify wiki page')),
('delete_wiki_page', _('Delete wiki page')),
# Wiki link permissions
('view_wiki_links', _('View wiki links')),
('add_wiki_link', _('Add wiki link')),
('modify_wiki_link', _('Modify wiki link')),
('delete_wiki_link', _('Delete wiki link')),
]
OWNERS_PERMISSIONS = [
('modify_project', _('Modify project')),
('add_member', _('Add member')),
('remove_member', _('Remove member')),
('delete_project', _('Delete project')),
('admin_project_values', _('Admin project values')),
('admin_roles', _('Admin roles')),
]

View File

@ -0,0 +1,75 @@
from taiga.projects.models import Membership, Project
from .permissions import OWNERS_PERMISSIONS, MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
def _get_user_project_membership(user, project):
if user.is_anonymous():
return None
try:
return Membership.objects.get(user=user, project=project)
except Membership.DoesNotExist:
return None
def _get_object_project(obj):
project = None
if isinstance(obj, Project):
project = obj
elif obj and hasattr(obj, 'project'):
project = obj.project
return project
def is_project_owner(user, obj):
if user.is_superuser:
return True
project = _get_object_project(obj)
if project:
return project.owner == user
return False
def user_has_perm(user, perm, obj=None):
project = _get_object_project(obj)
if not project:
return False
return perm in get_user_project_permissions(user, project)
def role_has_perm(role, perm):
return perm in role.permissions
def _get_membership_permissions(membership):
if membership and membership.role and membership.role.permissions:
return membership.role.permissions
return []
def get_user_project_permissions(user, project):
membership = _get_user_project_membership(user, project)
if user.is_superuser:
owner_permissions = list(map(lambda perm: perm[0], OWNERS_PERMISSIONS))
members_permissions = list(map(lambda perm: perm[0], MEMBERS_PERMISSIONS))
anon_permissions = list(map(lambda perm: perm[0], ANON_PERMISSIONS))
public_permissions = list(map(lambda perm: perm[0], USER_PERMISSIONS))
return set(owner_permissions + members_permissions + public_permissions + anon_permissions)
elif project.owner == user:
owner_permissions = list(map(lambda perm: perm[0], OWNERS_PERMISSIONS))
members_permissions = list(map(lambda perm: perm[0], MEMBERS_PERMISSIONS))
return set(project.anon_permissions + project.public_permissions + members_permissions + owner_permissions)
elif membership:
if membership.is_owner:
owner_permissions = list(map(lambda perm: perm[0], OWNERS_PERMISSIONS))
return set(project.anon_permissions + project.public_permissions + _get_membership_permissions(membership) + owner_permissions)
else:
return set(project.anon_permissions + project.public_permissions + _get_membership_permissions(membership))
elif user.is_authenticated():
return set(project.anon_permissions + project.public_permissions)
else:
return set(project.anon_permissions)

View File

@ -20,7 +20,6 @@ from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
from rest_framework import viewsets from rest_framework import viewsets
@ -30,9 +29,11 @@ from djmail.template_mail import MagicMailBuilder
from taiga.base import filters from taiga.base import filters
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base.decorators import list_route, detail_route from taiga.base.decorators import list_route
from taiga.base.permissions import has_project_perm from taiga.base.decorators import detail_route
from taiga.base.api import ModelCrudViewSet, ModelListViewSet, RetrieveModelMixin from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.mixins import RetrieveModelMixin
from taiga.base.api.permissions import IsAuthenticatedPermission, AllowAnyPermission
from taiga.base.utils.slug import slugify_uniquely from taiga.base.utils.slug import slugify_uniquely
from taiga.users.models import Role from taiga.users.models import Role
@ -40,20 +41,95 @@ from . import serializers
from . import models from . import models
from . import permissions from . import permissions
from . import services from . import services
from .votes.utils import attach_votescount_to_queryset from .votes.utils import attach_votescount_to_queryset
from .votes import services as votes_service from .votes import services as votes_service
from .votes import serializers as votes_serializers from .votes import serializers as votes_serializers
class ProjectAdminViewSet(ModelCrudViewSet): class ProjectViewSet(ModelCrudViewSet):
serializer_class = serializers.ProjectDetailSerializer serializer_class = serializers.ProjectDetailSerializer
list_serializer_class = serializers.ProjectSerializer list_serializer_class = serializers.ProjectSerializer
permission_classes = (IsAuthenticated, permissions.ProjectAdminPermission) permission_classes = (permissions.ProjectPermission, )
filter_backends = (filters.CanViewProjectObjFilterBackend,)
def get_queryset(self): def get_queryset(self):
qs = models.Project.objects.all() qs = models.Project.objects.all()
qs = attach_votescount_to_queryset(qs, as_field="stars_count") return attach_votescount_to_queryset(qs, as_field="stars_count")
return qs
@detail_route(methods=['get'])
def stats(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, 'stats', project)
return Response(services.get_stats_for_project(project))
@detail_route(methods=['post'])
def star(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, 'star', project)
votes_service.add_vote(project, user=request.user)
return Response(status=status.HTTP_200_OK)
@detail_route(methods=['post'])
def unstar(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, 'unstar', project)
votes_service.remove_vote(project, user=request.user)
return Response(status=status.HTTP_200_OK)
@detail_route(methods=['get'])
def issues_stats(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, 'issues_stats', project)
return Response(services.get_stats_for_project_issues(project))
@detail_route(methods=['get'])
def issue_filters_data(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, 'issues_filters_data', project)
return Response(services.get_issues_filters_data(project))
@detail_route(methods=['get'])
def tags(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, 'tags', project)
return Response(services.get_all_tags(project))
@detail_route(methods=['get'])
def fans(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, 'fans', project)
voters = votes_service.get_voters(project)
voters_data = votes_serializers.VoterSerializer(voters, many=True)
return Response(voters_data.data)
@detail_route(methods=["POST"])
def create_template(self, request, **kwargs):
template_name = request.DATA.get('template_name', None)
template_description = request.DATA.get('template_description', None)
if not template_name:
raise ParseError("Not valid template name")
if not template_description:
raise ParseError("Not valid template description")
template_slug = slugify_uniquely(template_name, models.ProjectTemplate)
project = self.get_object()
self.check_permissions(request, 'create_template', project)
template = models.ProjectTemplate(
name=template_name,
slug=template_slug,
description=template_description,
)
template.load_data_from_project(project)
template.save()
return Response(serializers.ProjectTemplateSerializer(template).data, status=201)
def pre_save(self, obj): def pre_save(self, obj):
if not obj.id: if not obj.id:
@ -66,61 +142,11 @@ class ProjectAdminViewSet(ModelCrudViewSet):
super().pre_save(obj) super().pre_save(obj)
class ProjectViewSet(ModelCrudViewSet):
serializer_class = serializers.ProjectDetailSerializer
list_serializer_class = serializers.ProjectSerializer
permission_classes = (IsAuthenticated, permissions.ProjectPermission)
def get_queryset(self):
qs = models.Project.objects.all()
qs = attach_votescount_to_queryset(qs, as_field="stars_count")
qs = qs.filter(Q(owner=self.request.user) |
Q(members=self.request.user))
return qs.distinct()
@detail_route(methods=['get'])
def stats(self, request, pk=None):
project = self.get_object()
return Response(services.get_stats_for_project(project))
@detail_route(methods=['post'], permission_classes=(IsAuthenticated,))
def star(self, request, pk=None):
project = self.get_object()
votes_service.add_vote(project, user=request.user)
return Response(status=status.HTTP_200_OK)
@detail_route(methods=['post'], permission_classes=(IsAuthenticated,))
def unstar(self, request, pk=None):
project = self.get_object()
votes_service.remove_vote(project, user=request.user)
return Response(status=status.HTTP_200_OK)
@detail_route(methods=['get'])
def issues_stats(self, request, pk=None):
project = self.get_object()
return Response(services.get_stats_for_project_issues(project))
@detail_route(methods=['get'])
def issue_filters_data(self, request, pk=None):
project = self.get_object()
return Response(services.get_issues_filters_data(project))
@detail_route(methods=['get'])
def tags(self, request, pk=None):
project = self.get_object()
return Response(services.get_all_tags(project))
def pre_save(self, obj):
if not obj.id:
obj.owner = self.request.user
super().pre_save(obj)
class MembershipViewSet(ModelCrudViewSet): class MembershipViewSet(ModelCrudViewSet):
model = models.Membership model = models.Membership
serializer_class = serializers.MembershipSerializer serializer_class = serializers.MembershipSerializer
permission_classes = (IsAuthenticated, permissions.MembershipPermission) permission_classes = (permissions.MembershipPermission,)
filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ("project", "role") filter_fields = ("project", "role")
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
@ -164,14 +190,14 @@ class MembershipViewSet(ModelCrudViewSet):
email.send() email.send()
class InvitationViewSet(RetrieveModelMixin, viewsets.ReadOnlyModelViewSet): class InvitationViewSet(ModelListViewSet):
""" """
Only used by front for get invitation by it token. Only used by front for get invitation by it token.
""" """
queryset = models.Membership.objects.filter(user__isnull=True) queryset = models.Membership.objects.filter(user__isnull=True)
serializer_class = serializers.MembershipSerializer serializer_class = serializers.MembershipSerializer
lookup_field = "token" lookup_field = "token"
permission_classes = (AllowAny,) permission_classes = (AllowAnyPermission,)
def list(self, *args, **kwargs): def list(self, *args, **kwargs):
raise exc.PermissionDenied(_("You don't have permisions to see that.")) raise exc.PermissionDenied(_("You don't have permisions to see that."))
@ -180,13 +206,10 @@ class InvitationViewSet(RetrieveModelMixin, viewsets.ReadOnlyModelViewSet):
class RolesViewSet(ModelCrudViewSet): class RolesViewSet(ModelCrudViewSet):
model = Role model = Role
serializer_class = serializers.RoleSerializer serializer_class = serializers.RoleSerializer
permission_classes = (IsAuthenticated, permissions.RolesPermission) permission_classes = (permissions.RolesPermission, )
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',) filter_fields = ('project',)
def get_queryset(self):
return self.model.objects.all().prefetch_related('permissions')
# User Stories commin ViewSets # User Stories commin ViewSets
@ -213,139 +236,93 @@ class BulkUpdateOrderMixin(object):
project = get_object_or_404(models.Project, id=project_id) project = get_object_or_404(models.Project, id=project_id)
if request.user != project.owner and not has_project_perm(request.user, project, self.bulk_update_perm): self.check_permissions(request, 'bulk_update_order', project)
raise exc.PermissionDenied(_("You don't have permisions %s.") % self.bulk_update_perm)
self.bulk_update_order(project, request.user, bulk_data) self.__class__.bulk_update_order_action(project, request.user, bulk_data)
return Response(data=None, status=status.HTTP_204_NO_CONTENT) return Response(data=None, status=status.HTTP_204_NO_CONTENT)
class PointsViewSet(ModelCrudViewSet, BulkUpdateOrderMixin): class PointsViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.Points model = models.Points
serializer_class = serializers.PointsSerializer serializer_class = serializers.PointsSerializer
permission_classes = (IsAuthenticated, permissions.PointsPermission) permission_classes = (permissions.PointsPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',) filter_fields = ('project',)
bulk_update_param = "bulk_points" bulk_update_param = "bulk_points"
bulk_update_perm = "change_points" bulk_update_perm = "change_points"
bulk_update_order = services.bulk_update_points_order bulk_update_order_action = services.bulk_update_points_order
class UserStoryStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin): class UserStoryStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.UserStoryStatus model = models.UserStoryStatus
serializer_class = serializers.UserStoryStatusSerializer serializer_class = serializers.UserStoryStatusSerializer
permission_classes = (IsAuthenticated, permissions.UserStoryStatusPermission) permission_classes = (permissions.UserStoryStatusPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',) filter_fields = ('project',)
bulk_update_param = "bulk_userstory_statuses" bulk_update_param = "bulk_userstory_statuses"
bulk_update_perm = "change_userstorystatus" bulk_update_perm = "change_userstorystatus"
bulk_update_order = services.bulk_update_userstory_status_order bulk_update_order_action = services.bulk_update_userstory_status_order
class TaskStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin): class TaskStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.TaskStatus model = models.TaskStatus
serializer_class = serializers.TaskStatusSerializer serializer_class = serializers.TaskStatusSerializer
permission_classes = (IsAuthenticated, permissions.TaskStatusPermission) permission_classes = (permissions.TaskStatusPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ("project",) filter_fields = ("project",)
bulk_update_param = "bulk_task_statuses" bulk_update_param = "bulk_task_statuses"
bulk_update_perm = "change_taskstatus" bulk_update_perm = "change_taskstatus"
bulk_update_order = services.bulk_update_task_status_order bulk_update_order_action = services.bulk_update_task_status_order
class SeverityViewSet(ModelCrudViewSet, BulkUpdateOrderMixin): class SeverityViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.Severity model = models.Severity
serializer_class = serializers.SeveritySerializer serializer_class = serializers.SeveritySerializer
permission_classes = (IsAuthenticated, permissions.SeverityPermission) permission_classes = (permissions.SeverityPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ("project",) filter_fields = ("project",)
bulk_update_param = "bulk_severities" bulk_update_param = "bulk_severities"
bulk_update_perm = "change_severity" bulk_update_perm = "change_severity"
bulk_update_order = services.bulk_update_severity_order bulk_update_order_action = services.bulk_update_severity_order
class PriorityViewSet(ModelCrudViewSet, BulkUpdateOrderMixin): class PriorityViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.Priority model = models.Priority
serializer_class = serializers.PrioritySerializer serializer_class = serializers.PrioritySerializer
permission_classes = (IsAuthenticated, permissions.PriorityPermission) permission_classes = (permissions.PriorityPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ("project",) filter_fields = ("project",)
bulk_update_param = "bulk_priorities" bulk_update_param = "bulk_priorities"
bulk_update_perm = "change_priority" bulk_update_perm = "change_priority"
bulk_update_order = services.bulk_update_priority_order bulk_update_order_action = services.bulk_update_priority_order
class IssueTypeViewSet(ModelCrudViewSet, BulkUpdateOrderMixin): class IssueTypeViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.IssueType model = models.IssueType
serializer_class = serializers.IssueTypeSerializer serializer_class = serializers.IssueTypeSerializer
permission_classes = (IsAuthenticated, permissions.IssueTypePermission) permission_classes = (permissions.IssueTypePermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ("project",) filter_fields = ("project",)
bulk_update_param = "bulk_issue_types" bulk_update_param = "bulk_issue_types"
bulk_update_perm = "change_issuetype" bulk_update_perm = "change_issuetype"
bulk_update_order = services.bulk_update_issue_type_order bulk_update_order_action = services.bulk_update_issue_type_order
class IssueStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin): class IssueStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.IssueStatus model = models.IssueStatus
serializer_class = serializers.IssueStatusSerializer serializer_class = serializers.IssueStatusSerializer
permission_classes = (IsAuthenticated, permissions.IssueStatusPermission) permission_classes = (permissions.IssueStatusPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ("project",) filter_fields = ("project",)
bulk_update_param = "bulk_issue_statuses" bulk_update_param = "bulk_issue_statuses"
bulk_update_perm = "change_issuestatus" bulk_update_perm = "change_issuestatus"
bulk_update_order = services.bulk_update_issue_status_order bulk_update_order_action = services.bulk_update_issue_status_order
class ProjectTemplateViewSet(ModelCrudViewSet): class ProjectTemplateViewSet(ModelCrudViewSet):
model = models.ProjectTemplate model = models.ProjectTemplate
serializer_class = serializers.ProjectTemplateSerializer serializer_class = serializers.ProjectTemplateSerializer
permission_classes = (IsAuthenticated, permissions.ProjectTemplatePermission) permission_classes = (permissions.ProjectTemplatePermission,)
@list_route(methods=["POST"])
def create_from_project(self, request, **kwargs):
project_id = request.DATA.get('project_id', None)
template_name = request.DATA.get('template_name', None)
template_description = request.DATA.get('template_description', None)
if not template_name:
raise ParseError("Not valid template name")
template_slug = slugify_uniquely(template_name, models.ProjectTemplate)
try:
project = models.Project.objects.get(pk=project_id)
except models.Project.DoesNotExist:
raise ParseError("Not valid project_id")
template = models.ProjectTemplate(
name=template_name,
slug=template_slug,
description=template_description,
)
template.load_data_from_project(project)
template.save()
return Response(self.serializer_class(template).data, status=201)
def get_queryset(self): def get_queryset(self):
return models.ProjectTemplate.objects.all() return models.ProjectTemplate.objects.all()
class FansViewSet(ModelCrudViewSet):
serializer_class = votes_serializers.VoterSerializer
list_serializer_class = votes_serializers.VoterSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
project = models.Project.objects.get(pk=self.kwargs.get("project_id"))
return votes_service.get_voters(project)
class StarredViewSet(ModelCrudViewSet):
serializer_class = serializers.StarredSerializer
list_serializer_class = serializers.StarredSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return votes_service.get_voted(self.kwargs.get("user_id"), model=models.Project)

View File

@ -14,15 +14,18 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.permissions import IsAuthenticated from django.conf import settings
from django import http
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet
from taiga.base.api import generics
from taiga.base import filters from taiga.base import filters
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.projects.history.services import take_snapshot
from taiga.projects.notifications import WatchedResourceMixin from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.history import HistoryResourceMixin from taiga.projects.history import HistoryResourceMixin
@ -36,9 +39,6 @@ from . import models
class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet): class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
model = models.Attachment model = models.Attachment
serializer_class = serializers.AttachmentSerializer serializer_class = serializers.AttachmentSerializer
permission_classes = (IsAuthenticated, permissions.AttachmentPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,)
filter_fields = ["project", "object_id"] filter_fields = ["project", "object_id"]
content_type = None content_type = None
@ -47,12 +47,6 @@ class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCru
app_name, model = self.content_type.split(".", 1) app_name, model = self.content_type.split(".", 1)
return get_object_or_404(ContentType, app_label=app_name, model=model) return get_object_or_404(ContentType, app_label=app_name, model=model)
def get_queryset(self):
ct = self.get_content_type()
qs = super().get_queryset()
qs = qs.filter(content_type=ct)
return qs.distinct()
def pre_save(self, obj): def pre_save(self, obj):
if not obj.id: if not obj.id:
obj.content_type = self.get_content_type() obj.content_type = self.get_content_type()
@ -60,19 +54,13 @@ class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCru
super().pre_save(obj) super().pre_save(obj)
def pre_conditions_on_save(self, obj):
super().pre_conditions_on_save(obj)
if (obj.project.owner != self.request.user and
obj.project.memberships.filter(user=self.request.user).count() == 0):
raise exc.PermissionDenied(_("You don't have permissions for "
"add attachments to this user story"))
def get_object_for_snapshot(self, obj): def get_object_for_snapshot(self, obj):
return obj.content_object return obj.content_object
class UserStoryAttachmentViewSet(BaseAttachmentViewSet): class UserStoryAttachmentViewSet(BaseAttachmentViewSet):
permission_classes = (permissions.UserStoryAttachmentPermission,)
filter_backends = (filters.CanViewUserStoryAttachmentFilterBackend,)
content_type = "userstories.userstory" content_type = "userstories.userstory"
create_notification_template = "create_userstory_notification" create_notification_template = "create_userstory_notification"
update_notification_template = "update_userstory_notification" update_notification_template = "update_userstory_notification"
@ -80,6 +68,8 @@ class UserStoryAttachmentViewSet(BaseAttachmentViewSet):
class IssueAttachmentViewSet(BaseAttachmentViewSet): class IssueAttachmentViewSet(BaseAttachmentViewSet):
permission_classes = (permissions.IssueAttachmentPermission,)
filter_backends = (filters.CanViewIssueAttachmentFilterBackend,)
content_type = "issues.issue" content_type = "issues.issue"
create_notification_template = "create_issue_notification" create_notification_template = "create_issue_notification"
update_notification_template = "update_issue_notification" update_notification_template = "update_issue_notification"
@ -87,6 +77,8 @@ class IssueAttachmentViewSet(BaseAttachmentViewSet):
class TaskAttachmentViewSet(BaseAttachmentViewSet): class TaskAttachmentViewSet(BaseAttachmentViewSet):
permission_classes = (permissions.TaskAttachmentPermission,)
filter_backends = (filters.CanViewTaskAttachmentFilterBackend,)
content_type = "tasks.task" content_type = "tasks.task"
create_notification_template = "create_task_notification" create_notification_template = "create_task_notification"
update_notification_template = "update_task_notification" update_notification_template = "update_task_notification"
@ -94,7 +86,31 @@ class TaskAttachmentViewSet(BaseAttachmentViewSet):
class WikiAttachmentViewSet(BaseAttachmentViewSet): class WikiAttachmentViewSet(BaseAttachmentViewSet):
permission_classes = (permissions.WikiAttachmentPermission,)
filter_backends = (filters.CanViewWikiAttachmentFilterBackend,)
content_type = "wiki.wikipage" content_type = "wiki.wikipage"
create_notification_template = "create_wiki_notification" create_notification_template = "create_wiki_notification"
update_notification_template = "update_wiki_notification" update_notification_template = "update_wiki_notification"
destroy_notification_template = "destroy_wiki_notification" destroy_notification_template = "destroy_wiki_notification"
class RawAttachmentView(generics.RetrieveAPIView):
queryset = models.Attachment.objects.all()
permission_classes = (permissions.RawAttachmentPermission,)
def _serve_attachment(self, attachment):
if settings.IN_DEVELOPMENT_SERVER:
return http.HttpResponseRedirect(attachment.url)
name = attachment.name
response = http.HttpResponse()
response['X-Accel-Redirect'] = "/{filepath}".format(filepath=name)
response['Content-Disposition'] = 'attachment;filename={filename}'.format(
filename=os.path.basename(name))
return response
def retrieve(self, request, *args, **kwargs):
self.object = self.get_object()
self.check_permissions(request, 'retrieve', self.object)
return self._serve_attachment(self.object.attached_file)

View File

@ -15,14 +15,62 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.permissions import BasePermission from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny, PermissionComponent)
class AttachmentPermission(BasePermission): class IsAttachmentOwnerPerm(PermissionComponent):
get_permission = "view_attachment" def check_permissions(self, request, view, obj=None):
post_permission = "add_attachment" if obj and obj.owner and request.user.is_authenticated():
put_permission = "change_attachment" return request.user == obj.owner
patch_permission = "change_attachment" return False
delete_permission = "delete_attachment"
safe_methods = ["HEAD", "OPTIONS"]
path_to_project = ["project"] class UserStoryAttachmentPermission(ResourcePermission):
retrieve_perms = HasProjectPerm('view_us') | IsAttachmentOwnerPerm()
create_perms = HasProjectPerm('modify_us')
update_perms = HasProjectPerm('modify_us') | IsAttachmentOwnerPerm()
destroy_perms = HasProjectPerm('modify_us') | IsAttachmentOwnerPerm()
list_perms = AllowAny()
class TaskAttachmentPermission(ResourcePermission):
retrieve_perms = HasProjectPerm('view_tasks') | IsAttachmentOwnerPerm()
create_perms = HasProjectPerm('modify_task')
update_perms = HasProjectPerm('modify_task') | IsAttachmentOwnerPerm()
destroy_perms = HasProjectPerm('modify_task') | IsAttachmentOwnerPerm()
list_perms = AllowAny()
class IssueAttachmentPermission(ResourcePermission):
retrieve_perms = HasProjectPerm('view_issues') | IsAttachmentOwnerPerm()
create_perms = HasProjectPerm('modify_issue')
update_perms = HasProjectPerm('modify_issue') | IsAttachmentOwnerPerm()
destroy_perms = HasProjectPerm('modify_issue') | IsAttachmentOwnerPerm()
list_perms = AllowAny()
class WikiAttachmentPermission(ResourcePermission):
retrieve_perms = HasProjectPerm('view_wiki_pages') | IsAttachmentOwnerPerm()
create_perms = HasProjectPerm('modify_wiki_page')
update_perms = HasProjectPerm('modify_wiki_page') | IsAttachmentOwnerPerm()
destroy_perms = HasProjectPerm('modify_wiki_page') | IsAttachmentOwnerPerm()
list_perms = AllowAny()
class RawAttachmentPerm(PermissionComponent):
def check_permissions(self, request, view, obj=None):
is_owner = IsAttachmentOwnerPerm().check_permissions(request, view, obj)
if obj.content_type.app_label == "userstories" and obj.content_type.model == "userstory":
return UserStoryAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
elif obj.content_type.app_label == "tasks" and obj.content_type.model == "task":
return TaskAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
elif obj.content_type.app_label == "issues" and obj.content_type.model == "issue":
return IssueAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
elif obj.content_type.app_label == "wiki" and obj.content_type.model == "wikipage":
return WikiAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
return False
class RawAttachmentPermission(ResourcePermission):
retrieve_perms = RawAttachmentPerm()

View File

@ -1,32 +0,0 @@
import os
from django.conf import settings
from django import http
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from . import permissions
from . import models
def serve_attachment(request, attachment):
if settings.IN_DEVELOPMENT_SERVER:
return http.HttpResponseRedirect(attachment.url)
name = attachment.name
response = http.HttpResponse()
response['X-Accel-Redirect'] = "/{filepath}".format(filepath=name)
response['Content-Disposition'] = 'attachment;filename={filename}'.format(
filename=os.path.basename(name))
return response
class RawAttachmentView(generics.RetrieveAPIView):
queryset = models.Attachment.objects.all()
permission_classes = (IsAuthenticated, permissions.AttachmentPermission,)
def retrieve(self, request, *args, **kwargs):
self.object = self.get_object()
return serve_attachment(request, self.object.attached_file)

View File

@ -31,8 +31,6 @@ from . import services
# TODO: add specific permission for view history? # TODO: add specific permission for view history?
class HistoryViewSet(GenericViewSet): class HistoryViewSet(GenericViewSet):
filter_backends = (IsProjectMemberFilterBackend,)
permission_classes = (IsAuthenticated, permissions.HistoryPermission)
serializer_class = serializers.HistoryEntrySerializer serializer_class = serializers.HistoryEntrySerializer
content_type = None content_type = None
@ -41,13 +39,13 @@ class HistoryViewSet(GenericViewSet):
app_name, model = self.content_type.split(".", 1) app_name, model = self.content_type.split(".", 1)
return get_object_or_404(ContentType, app_label=app_name, model=model) return get_object_or_404(ContentType, app_label=app_name, model=model)
def get_object(self): def get_queryset(self):
ct = self.get_content_type() ct = self.get_content_type()
model_cls = ct.model_class() model_cls = ct.model_class()
qs = model_cls.objects.all() qs = model_cls.objects.all()
filtered_qs = self.filter_queryset(qs) filtered_qs = self.filter_queryset(qs)
return super().get_object(queryset=filtered_qs) return filtered_qs
def response_for_queryset(self, queryset): def response_for_queryset(self, queryset):
# Switch between paginated or standard style responses # Switch between paginated or standard style responses
@ -66,21 +64,27 @@ class HistoryViewSet(GenericViewSet):
def retrieve(self, request, pk): def retrieve(self, request, pk):
obj = self.get_object() obj = self.get_object()
self.check_permissions(request, "retrieve", obj)
qs = services.get_history_queryset_by_model_instance(obj) qs = services.get_history_queryset_by_model_instance(obj)
return self.response_for_queryset(qs) return self.response_for_queryset(qs)
class UserStoryHistory(HistoryViewSet): class UserStoryHistory(HistoryViewSet):
content_type = "userstories.userstory" content_type = "userstories.userstory"
permission_classes = (permissions.UserStoryHistoryPermission,)
class TaskHistory(HistoryViewSet): class TaskHistory(HistoryViewSet):
content_type = "tasks.task" content_type = "tasks.task"
permission_classes = (permissions.TaskHistoryPermission,)
class IssueHistory(HistoryViewSet): class IssueHistory(HistoryViewSet):
content_type = "issues.issue" content_type = "issues.issue"
permission_classes = (permissions.IssueHistoryPermission,)
class WikiHistory(HistoryViewSet): class WikiHistory(HistoryViewSet):
content_type = "wiki.wiki" content_type = "wiki.wikipage"
permission_classes = (permissions.WikiHistoryPermission,)

View File

@ -1,4 +1,6 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be> # Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the # published by the Free Software Foundation, either version 3 of the
@ -12,9 +14,21 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.permissions import Permission from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny)
class HistoryPermission(Permission):
def has_object_permission(self, request, view, obj): class UserStoryHistoryPermission(ResourcePermission):
# TODO: change this. retrieve_perms = HasProjectPerm('view_project')
return True
class TaskHistoryPermission(ResourcePermission):
retrieve_perms = HasProjectPerm('view_project')
class IssueHistoryPermission(ResourcePermission):
retrieve_perms = HasProjectPerm('view_project')
class WikiHistoryPermission(ResourcePermission):
retrieve_perms = HasProjectPerm('view_project')

View File

@ -15,19 +15,21 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.db.models import Q from django.db.models import Q
from django.http import Http404
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from rest_framework import filters
from taiga.base import filters from taiga.base import filters
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base.decorators import list_route, detail_route from taiga.base.decorators import detail_route
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base import tags from taiga.base import tags
from taiga.users.models import User
from taiga.projects.notifications import WatchedResourceMixin from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.occ import OCCResourceMixin from taiga.projects.occ import OCCResourceMixin
from taiga.projects.history import HistoryResourceMixin from taiga.projects.history import HistoryResourceMixin
@ -42,7 +44,7 @@ from . import serializers
class IssuesFilter(filters.FilterBackend): class IssuesFilter(filters.FilterBackend):
filter_fields = ( "status", "severity", "priority", "owner", "assigned_to", "tags", "type") filter_fields = ("status", "severity", "priority", "owner", "assigned_to", "tags", "type")
_special_values_dict = { _special_values_dict = {
'true': True, 'true': True,
'false': False, 'false': False,
@ -80,7 +82,7 @@ class IssuesFilter(filters.FilterBackend):
for name, value in filter(lambda x: x[0] != "tags", filterdata.items()): for name, value in filter(lambda x: x[0] != "tags", filterdata.items()):
if None in value: if None in value:
qs_in_kwargs = {"{0}__in".format(name): [v for v in value if v != None]} qs_in_kwargs = {"{0}__in".format(name): [v for v in value if v is not None]}
qs_isnull_kwargs = {"{0}__isnull".format(name): True} qs_isnull_kwargs = {"{0}__isnull".format(name): True}
queryset = queryset.filter(Q(**qs_in_kwargs) | Q(**qs_isnull_kwargs)) queryset = queryset.filter(Q(**qs_in_kwargs) | Q(**qs_isnull_kwargs))
else: else:
@ -104,9 +106,9 @@ class IssuesOrdering(filters.FilterBackend):
class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet): class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
serializer_class = serializers.IssueNeighborsSerializer serializer_class = serializers.IssueNeighborsSerializer
list_serializer_class = serializers.IssueSerializer list_serializer_class = serializers.IssueSerializer
permission_classes = (IsAuthenticated, permissions.IssuePermission) permission_classes = (permissions.IssuePermission, )
filter_backends = (filters.IsProjectMemberFilterBackend, IssuesFilter, IssuesOrdering) filter_backends = (filters.CanViewIssuesFilterBackend, IssuesFilter, IssuesOrdering)
retrieve_exclude_filters = (IssuesFilter,) retrieve_exclude_filters = (IssuesFilter,)
filter_fields = ("project",) filter_fields = ("project",)
@ -133,42 +135,65 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
def pre_conditions_on_save(self, obj): def pre_conditions_on_save(self, obj):
super().pre_conditions_on_save(obj) super().pre_conditions_on_save(obj)
if (obj.project.owner != self.request.user and
obj.project.memberships.filter(user=self.request.user).count() == 0):
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue."))
if obj.milestone and obj.milestone.project != obj.project: if obj.milestone and obj.milestone.project != obj.project:
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue.")) raise exc.PermissionDenied(_("You don't have permissions to set this milestone to this issue."))
if obj.status and obj.status.project != obj.project: if obj.status and obj.status.project != obj.project:
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue.")) raise exc.PermissionDenied(_("You don't have permissions to set this status to this issue."))
if obj.severity and obj.severity.project != obj.project: if obj.severity and obj.severity.project != obj.project:
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue.")) raise exc.PermissionDenied(_("You don't have permissions to set this severity to this issue."))
if obj.priority and obj.priority.project != obj.project: if obj.priority and obj.priority.project != obj.project:
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue.")) raise exc.PermissionDenied(_("You don't have permissions to set this priority to this issue."))
if obj.type and obj.type.project != obj.project: if obj.type and obj.type.project != obj.project:
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue.")) raise exc.PermissionDenied(_("You don't have permissions to set this type to this issue."))
@detail_route(methods=['post'], permission_classes=(IsAuthenticated,)) @detail_route(methods=['post'])
def upvote(self, request, pk=None): def upvote(self, request, pk=None):
issue = self.get_object() issue = get_object_or_404(models.Issue, pk=pk)
self.check_permissions(request, 'upvote', issue)
votes_service.add_vote(issue, user=request.user) votes_service.add_vote(issue, user=request.user)
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)
@detail_route(methods=['post'], permission_classes=(IsAuthenticated,)) @detail_route(methods=['post'])
def downvote(self, request, pk=None): def downvote(self, request, pk=None):
issue = self.get_object() issue = get_object_or_404(models.Issue, pk=pk)
self.check_permissions(request, 'downvote', issue)
votes_service.remove_vote(issue, user=request.user) votes_service.remove_vote(issue, user=request.user)
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)
class VotersViewSet(ModelCrudViewSet): class VotersViewSet(ModelListViewSet):
serializer_class = votes_serializers.VoterSerializer serializer_class = votes_serializers.VoterSerializer
list_serializer_class = votes_serializers.VoterSerializer list_serializer_class = votes_serializers.VoterSerializer
permission_classes = (IsAuthenticated,) permission_classes = (permissions.IssueVotersPermission, )
def retrieve(self, request, *args, **kwargs):
pk = kwargs.get("pk", None)
issue_id = kwargs.get("issue_id", None)
issue = get_object_or_404(models.Issue, pk=issue_id)
self.check_permissions(request, 'retrieve', issue)
try:
self.object = votes_service.get_voters(issue).get(pk=pk)
except User.DoesNotExist:
raise Http404
serializer = self.get_serializer(self.object)
return Response(serializer.data)
def list(self, request, *args, **kwargs):
issue_id = kwargs.get("issue_id", None)
issue = get_object_or_404(models.Issue, pk=issue_id)
self.check_permissions(request, 'list', issue)
return super().list(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
issue = models.Issue.objects.get(pk=self.kwargs.get("issue_id")) issue = models.Issue.objects.get(pk=self.kwargs.get("issue_id"))

View File

@ -15,14 +15,36 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.permissions import BasePermission from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsProjectOwner, PermissionComponent,
AllowAny, IsAuthenticated)
class IssuePermission(BasePermission): class IssuePermission(ResourcePermission):
get_permission = "view_issue" enought_perms = IsProjectOwner()
post_permission = "add_issue" global_perms = None
put_permission = "change_issue" retrieve_perms = HasProjectPerm('view_issues')
patch_permission = "change_issue" create_perms = HasProjectPerm('add_issue')
delete_permission = "delete_issue" update_perms = HasProjectPerm('modify_issue')
safe_methods = ["HEAD", "OPTIONS"] destroy_perms = HasProjectPerm('delete_issue')
path_to_project = ["project"] list_perms = AllowAny()
upvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
downvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
class HasIssueIdUrlParam(PermissionComponent):
def check_permissions(self, request, view, obj=None):
param = view.kwargs.get('issue_id', None)
if param:
return True
return False
class IssueVotersPermission(ResourcePermission):
enought_perms = IsProjectOwner()
global_perms = None
retrieve_perms = HasProjectPerm('view_issues')
create_perms = HasProjectPerm('add_issue')
update_perms = HasProjectPerm('modify_issue')
destroy_perms = HasProjectPerm('delete_issue')
list_perms = HasProjectPerm('view_issues')

View File

@ -38,8 +38,8 @@ import datetime
class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet): class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
serializer_class = serializers.MilestoneSerializer serializer_class = serializers.MilestoneSerializer
permission_classes = (IsAuthenticated, permissions.MilestonePermission) permission_classes = (permissions.MilestonePermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewMilestonesFilterBackend,)
filter_fields = ("project",) filter_fields = ("project",)
def get_queryset(self): def get_queryset(self):
@ -51,13 +51,6 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
qs = qs.order_by("-estimated_start") qs = qs.order_by("-estimated_start")
return qs return qs
def pre_conditions_on_save(self, obj):
super().pre_conditions_on_save(obj)
if (obj.project.owner != self.request.user and
obj.project.memberships.filter(user=self.request.user).count() == 0):
raise exc.PreconditionError(_("You must not add a new milestone to this project."))
def pre_save(self, obj): def pre_save(self, obj):
if not obj.id: if not obj.id:
obj.owner = self.request.user obj.owner = self.request.user
@ -67,6 +60,9 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
@detail_route(methods=['get']) @detail_route(methods=['get'])
def stats(self, request, pk=None): def stats(self, request, pk=None):
milestone = get_object_or_404(models.Milestone, pk=pk) milestone = get_object_or_404(models.Milestone, pk=pk)
self.check_permissions(request, "stats", milestone)
total_points = milestone.total_points total_points = milestone.total_points
milestone_stats = { milestone_stats = {
'name': milestone.name, 'name': milestone.name,

View File

@ -14,15 +14,16 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.permissions import BasePermission from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny)
class MilestonePermission(BasePermission): class MilestonePermission(ResourcePermission):
get_permission = "view_milestone" enought_perms = IsProjectOwner()
post_permission = "add_milestone" global_perms = None
put_permission = "change_milestone" retrieve_perms = HasProjectPerm('view_milestones')
patch_permission = "change_milestone" create_perms = HasProjectPerm('add_milestone')
delete_permission = "delete_milestone" update_perms = HasProjectPerm('modify_milestone')
safe_methods = ["HEAD", "OPTIONS"] destroy_perms = HasProjectPerm('delete_milestone')
path_to_project = ["project"] list_perms = AllowAny()
stats_perms = HasProjectPerm('view_milestones')

View File

@ -52,7 +52,7 @@ class MilestoneSerializer(serializers.ModelSerializer):
qs = None qs = None
# If the milestone exists: # If the milestone exists:
if self.object and attrs.get("name", None): if self.object and attrs.get("name", None):
qs = models.Milestone.objects.filter(project=self.object.project, name=attrs[source]) qs = models.Milestone.objects.filter(project=self.object.project, name=attrs[source]).exclude(pk=self.object.pk)
if not self.object and attrs.get("project", None) and attrs.get("name", None): if not self.object and attrs.get("project", None) and attrs.get("name", None):
qs = models.Milestone.objects.filter(project=attrs["project"], name=attrs[source]) qs = models.Milestone.objects.filter(project=attrs["project"], name=attrs[source])

View File

@ -23,12 +23,13 @@ from django.db.models.loading import get_model
from django.conf import settings from django.conf import settings
from django.dispatch import receiver from django.dispatch import receiver
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from picklefield.fields import PickledObjectField from picklefield.fields import PickledObjectField
from django_pgjson.fields import JsonField from django_pgjson.fields import JsonField
from djorm_pgarray.fields import TextArrayField
from taiga.permissions.permissions import ANON_PERMISSIONS, USER_PERMISSIONS
from taiga.base.tags import TaggedMixin from taiga.base.tags import TaggedMixin
from taiga.users.models import Role from taiga.users.models import Role
@ -43,6 +44,7 @@ VIDEOCONFERENCES_CHOICES = (
('talky', 'Talky'), ('talky', 'Talky'),
) )
class Membership(models.Model): class Membership(models.Model):
# This model stores all project memberships. Also # This model stores all project memberships. Also
# stores invitations to memberships that does not have # stores invitations to memberships that does not have
@ -152,6 +154,16 @@ class Project(ProjectDefaults, TaggedMixin, models.Model):
related_name="projects", null=True, related_name="projects", null=True,
blank=True, default=None, blank=True, default=None,
verbose_name=_("creation template")) verbose_name=_("creation template"))
anon_permissions = TextArrayField(blank=True, null=True,
default=[],
verbose_name=_("anonymous permissions"),
choices=ANON_PERMISSIONS)
public_permissions = TextArrayField(blank=True, null=True,
default=[],
verbose_name=_("user permissions"),
choices=USER_PERMISSIONS)
is_private = models.BooleanField(default=False, null=False, blank=True,
verbose_name=_("is private"))
class Meta: class Meta:
verbose_name = "project" verbose_name = "project"
@ -193,7 +205,10 @@ class Project(ProjectDefaults, TaggedMixin, models.Model):
return return
# Get point instance that represent a null/undefined # Get point instance that represent a null/undefined
try:
null_points_value = self.points.get(value=None) null_points_value = self.points.get(value=None)
except Points.DoesNotExist:
null_points_value = None
# Iter over all project user stories and create # Iter over all project user stories and create
# role point instance for new created roles. # role point instance for new created roles.
@ -513,7 +528,6 @@ class ProjectTemplate(models.Model):
"severity": getattr(project.default_severity, "name", None) "severity": getattr(project.default_severity, "name", None)
} }
self.us_statuses = [] self.us_statuses = []
for us_status in project.us_statuses.all(): for us_status in project.us_statuses.all():
self.us_statuses.append({ self.us_statuses.append({
@ -576,17 +590,19 @@ class ProjectTemplate(models.Model):
self.roles = [] self.roles = []
for role in project.roles.all(): for role in project.roles.all():
permissions = [p.codename for p in role.permissions.all()]
self.roles.append({ self.roles.append({
"name": role.name, "name": role.name,
"slug": role.slug, "slug": role.slug,
"permissions": permissions, "permissions": role.permissions,
"order": role.order, "order": role.order,
"computable": role.computable "computable": role.computable
}) })
try:
owner_membership = Membership.objects.get(project=project, user=project.owner) owner_membership = Membership.objects.get(project=project, user=project.owner)
self.default_owner_role = owner_membership.role.slug self.default_owner_role = owner_membership.role.slug
except Membership.DoesNotExist:
self.default_owner_role = self.roles[0].get("slug", None)
def apply_to_project(self, project): def apply_to_project(self, project):
if project.id is None: if project.id is None:
@ -661,16 +677,14 @@ class ProjectTemplate(models.Model):
) )
for role in self.roles: for role in self.roles:
newRoleInstance = Role.objects.create( Role.objects.create(
name=role["name"], name=role["name"],
slug=role["slug"], slug=role["slug"],
order=role["order"], order=role["order"],
computable=role["computable"], computable=role["computable"],
project=project project=project,
permissions=role['permissions']
) )
permissions = [Permission.objects.get(codename=codename) for codename in role["permissions"]]
for permission in permissions:
newRoleInstance.permissions.add(permission)
if self.points: if self.points:
project.default_points = Points.objects.get(name=self.default_options["points"], project.default_points = Points.objects.get(name=self.default_options["points"],
@ -696,7 +710,6 @@ class ProjectTemplate(models.Model):
if self.severities: if self.severities:
project.default_severity = Severity.objects.get(name=self.default_options["severity"], project=project) project.default_severity = Severity.objects.get(name=self.default_options["severity"], project=project)
if self.default_owner_role: if self.default_owner_role:
# FIXME: is operation should to be on template apply method? # FIXME: is operation should to be on template apply method?
Membership.objects.create(user=project.owner, Membership.objects.create(user=project.owner,

View File

@ -14,129 +14,117 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.permissions import BasePermission from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsAuthenticated, IsProjectOwner,
AllowAny, IsSuperUser)
class ProjectPermission(BasePermission): class ProjectPermission(ResourcePermission):
get_permission = "view_project" retrieve_perms = HasProjectPerm('view_project')
post_permission = None create_perms = IsAuthenticated()
put_permission = "change_project" update_perms = IsProjectOwner()
patch_permission = "change_project" destroy_perms = IsProjectOwner()
delete_permission = None list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"] stats_perms = AllowAny()
path_to_project = [] star_perms = IsAuthenticated()
unstar_perms = IsAuthenticated()
issues_stats_perms = AllowAny()
issues_filters_data_perms = AllowAny()
tags_perms = AllowAny()
fans_perms = HasProjectPerm('view_project')
create_template_perms = IsSuperUser()
class ProjectAdminPermission(BasePermission): class MembershipPermission(ResourcePermission):
def has_permission(self, request, view): retrieve_perms = HasProjectPerm('view_project')
if request.method in self.safe_methods: create_perms = IsProjectOwner()
return True update_perms = IsProjectOwner()
return super().has_permission(request, view) destroy_perms = IsProjectOwner()
list_perms = AllowAny()
def has_object_permission(self, request, view, obj):
if request.method in self.safe_methods:
return True
return super().has_object_permission(request, view, obj)
class MembershipPermission(BasePermission):
get_permission = "view_membership"
post_permission = "add_membership"
put_permission = "change_membership"
patch_permission = "change_membership"
delete_permission = "delete_membership"
safe_methods = ["HEAD", "OPTIONS"]
path_to_project = ["project"]
# User Stories # User Stories
class PointsPermission(BasePermission): class PointsPermission(ResourcePermission):
get_permission = "view_points" retrieve_perms = HasProjectPerm('view_project')
post_permission = "add_points" create_perms = IsProjectOwner()
put_permission = "change_points" update_perms = IsProjectOwner()
patch_permission = "change_points" destroy_perms = IsProjectOwner()
delete_permission = "delete_points" list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"] bulk_update_order_perms = IsProjectOwner()
path_to_project = ["project"]
class UserStoryStatusPermission(BasePermission): class UserStoryStatusPermission(ResourcePermission):
get_permission = "view_userstorystatus" retrieve_perms = HasProjectPerm('view_project')
post_permission = "add_userstorystatus" create_perms = IsProjectOwner()
put_permission = "change_userstorystatus" update_perms = IsProjectOwner()
patch_permission = "change_userstorystatus" destroy_perms = IsProjectOwner()
delete_permission = "delete_userstorystatus" list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"] bulk_update_order_perms = IsProjectOwner()
path_to_project = ["project"]
# Tasks # Tasks
class TaskStatusPermission(BasePermission): class TaskStatusPermission(ResourcePermission):
get_permission = "view_taskstatus" retrieve_perms = HasProjectPerm('view_project')
post_permission = "ade_taskstatus" create_perms = IsProjectOwner()
put_permission = "change_taskstatus" update_perms = IsProjectOwner()
patch_permission = "change_taskstatus" destroy_perms = IsProjectOwner()
delete_permission = "delete_taskstatus" list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"] bulk_update_order_perms = IsProjectOwner()
path_to_project = ["project"]
# Issues # Issues
class SeverityPermission(BasePermission): class SeverityPermission(ResourcePermission):
get_permission = "view_severity" retrieve_perms = HasProjectPerm('view_project')
post_permission = "add_severity" create_perms = IsProjectOwner()
put_permission = "change_severity" update_perms = IsProjectOwner()
patch_permission = "change_severity" destroy_perms = IsProjectOwner()
delete_permission = "delete_severity" list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"] bulk_update_order_perms = IsProjectOwner()
path_to_project = ["project"]
class PriorityPermission(BasePermission): class PriorityPermission(ResourcePermission):
get_permission = "view_priority" retrieve_perms = HasProjectPerm('view_project')
post_permission = "add_priority" create_perms = IsProjectOwner()
put_permission = "change_priority" update_perms = IsProjectOwner()
patch_permission = "change_priority" destroy_perms = IsProjectOwner()
delete_permission = "delete_priority" list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"] bulk_update_order_perms = IsProjectOwner()
path_to_project = ["project"]
class IssueStatusPermission(BasePermission): class IssueStatusPermission(ResourcePermission):
get_permission = "view_issuestatus" retrieve_perms = HasProjectPerm('view_project')
post_permission = "add_issuestatus" create_perms = IsProjectOwner()
put_permission = "change_issuestatus" update_perms = IsProjectOwner()
patch_permission = "change_issuestatus" destroy_perms = IsProjectOwner()
delete_permission = "delete_issuestatus" list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"] bulk_update_order_perms = IsProjectOwner()
path_to_project = ["project"]
class IssueTypePermission(BasePermission): class IssueTypePermission(ResourcePermission):
get_permission = "view_issuetype" retrieve_perms = HasProjectPerm('view_project')
post_permission = "add_issuetype" create_perms = IsProjectOwner()
put_permission = "change_issuetype" update_perms = IsProjectOwner()
patch_permission = "change_issuetype" destroy_perms = IsProjectOwner()
delete_permission = "delete_issuetype" list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"] bulk_update_order_perms = IsProjectOwner()
path_to_project = ["project"]
class RolesPermission(BasePermission): class RolesPermission(ResourcePermission):
get_permission = "view_role" retrieve_perms = HasProjectPerm('view_project')
post_permission = "add_role" create_perms = IsProjectOwner()
put_permission = "change_role" update_perms = IsProjectOwner()
patch_permission = "change_role" destroy_perms = IsProjectOwner()
delete_permission = "delete_role" list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"]
path_to_project = ["project"]
# Project Templates # Project Templates
class ProjectTemplatePermission(BasePermission): class ProjectTemplatePermission(ResourcePermission):
# TODO: should be improved in permissions refactor retrieve_perms = AllowAny()
pass create_perms = IsSuperUser()
update_perms = IsSuperUser()
destroy_perms = IsSuperUser()
list_perms = AllowAny()

View File

@ -18,15 +18,17 @@ from django.db.models.loading import get_model
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base.api import viewsets
from .serializers import ResolverSerializer from .serializers import ResolverSerializer
from taiga.permissions.service import user_has_perm
from . import permissions
class ResolverViewSet(viewsets.ViewSet): class ResolverViewSet(viewsets.ViewSet):
permission_classes = (IsAuthenticated,) permission_classes = (permissions.ResolverPermission,)
def list(self, request, **kwargs): def list(self, request, **kwargs):
serializer = ResolverSerializer(data=request.QUERY_PARAMS) serializer = ResolverSerializer(data=request.QUERY_PARAMS)
@ -38,17 +40,19 @@ class ResolverViewSet(viewsets.ViewSet):
project_model = get_model("projects", "Project") project_model = get_model("projects", "Project")
project = get_object_or_404(project_model, slug=data["project"]) project = get_object_or_404(project_model, slug=data["project"])
self.check_permissions(request, "list", project)
result = { result = {
"project": project.pk "project": project.pk
} }
if data["us"]: if data["us"] and user_has_perm(request.user, "view_us", project):
result["us"] = get_object_or_404(project.user_stories.all(), ref=data["us"]).pk result["us"] = get_object_or_404(project.user_stories.all(), ref=data["us"]).pk
if data["task"]: if data["task"] and user_has_perm(request.user, "view_tasks", project):
result["task"] = get_object_or_404(project.tasks.all(), ref=data["task"]).pk result["task"] = get_object_or_404(project.tasks.all(), ref=data["task"]).pk
if data["issue"]: if data["issue"] and user_has_perm(request.user, "view_issues", project):
result["issue"] = get_object_or_404(project.issues.all(), ref=data["issue"]).pk result["issue"] = get_object_or_404(project.issues.all(), ref=data["issue"]).pk
if data["milestone"]: if data["milestone"] and user_has_perm(request.user, "view_milestones", project):
result["milestone"] = get_object_or_404(project.milestones.all(), slug=data["milestone"]).pk result["milestone"] = get_object_or_404(project.milestones.all(), slug=data["milestone"]).pk
return Response(result) return Response(result)

View File

@ -0,0 +1,22 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny)
class ResolverPermission(ResourcePermission):
list_perms = HasProjectPerm('view_project')

View File

@ -18,10 +18,10 @@ from os import path
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from taiga.base.serializers import PickleField, JsonField from taiga.base.serializers import JsonField, PgArrayField
from taiga.users.serializers import UserSerializer
from taiga.users.models import Role, User from taiga.users.models import Role, User
from taiga.users.services import get_photo_or_gravatar_url from taiga.users.services import get_photo_or_gravatar_url
from taiga.users.serializers import UserSerializer
from . import models from . import models
@ -131,6 +131,9 @@ class ProjectMembershipSerializer(serializers.ModelSerializer):
class ProjectSerializer(serializers.ModelSerializer): class ProjectSerializer(serializers.ModelSerializer):
tags = PgArrayField(required=False)
anon_permissions = PgArrayField(required=False)
public_permissions = PgArrayField(required=False)
stars = serializers.SerializerMethodField("get_stars_number") stars = serializers.SerializerMethodField("get_stars_number")
class Meta: class Meta:
@ -143,10 +146,16 @@ class ProjectSerializer(serializers.ModelSerializer):
return getattr(obj, "stars_count", 0) return getattr(obj, "stars_count", 0)
def validate_slug(self, attrs, source): def validate_slug(self, attrs, source):
if self.object:
project_with_slug = models.Project.objects.filter(slug=attrs[source]).exclude(pk=self.object.pk)
else:
project_with_slug = models.Project.objects.filter(slug=attrs[source]) project_with_slug = models.Project.objects.filter(slug=attrs[source])
if source == "slug" and project_with_slug.exists(): if source == "slug" and project_with_slug.exists():
raise serializers.ValidationError(_("Slug duplicated for the project")) raise serializers.ValidationError(_("Slug duplicated for the project"))
return attrs
class ProjectDetailSerializer(ProjectSerializer): class ProjectDetailSerializer(ProjectSerializer):
roles = serializers.SerializerMethodField("get_list_of_roles") roles = serializers.SerializerMethodField("get_list_of_roles")
@ -187,6 +196,8 @@ class ProjectRoleSerializer(serializers.ModelSerializer):
class RoleSerializer(serializers.ModelSerializer): class RoleSerializer(serializers.ModelSerializer):
permissions = PgArrayField(required=False)
class Meta: class Meta:
model = Role model = Role
fields = ('id', 'name', 'permissions', 'computable', 'project', 'order') fields = ('id', 'name', 'permissions', 'computable', 'project', 'order')

View File

@ -31,6 +31,7 @@ def bulk_update_userstory_status_order(project, user, data):
for id, order in data: for id, order in data:
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);", cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id)) (order, id, project.id))
cursor.execute("DEALLOCATE bulk_update_order")
cursor.close() cursor.close()
@ -48,6 +49,7 @@ def bulk_update_points_order(project, user, data):
for id, order in data: for id, order in data:
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);", cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id)) (order, id, project.id))
cursor.execute("DEALLOCATE bulk_update_order")
cursor.close() cursor.close()
@ -65,6 +67,7 @@ def bulk_update_task_status_order(project, user, data):
for id, order in data: for id, order in data:
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);", cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id)) (order, id, project.id))
cursor.execute("DEALLOCATE bulk_update_order")
cursor.close() cursor.close()
@ -82,6 +85,7 @@ def bulk_update_issue_status_order(project, user, data):
for id, order in data: for id, order in data:
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);", cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id)) (order, id, project.id))
cursor.execute("DEALLOCATE bulk_update_order")
cursor.close() cursor.close()
@ -99,6 +103,7 @@ def bulk_update_issue_type_order(project, user, data):
for id, order in data: for id, order in data:
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);", cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id)) (order, id, project.id))
cursor.execute("DEALLOCATE bulk_update_order")
cursor.close() cursor.close()
@ -116,6 +121,7 @@ def bulk_update_priority_order(project, user, data):
for id, order in data: for id, order in data:
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);", cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id)) (order, id, project.id))
cursor.execute("DEALLOCATE bulk_update_order")
cursor.close() cursor.close()
@ -133,4 +139,5 @@ def bulk_update_severity_order(project, user, data):
for id, order in data: for id, order in data:
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);", cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id)) (order, id, project.id))
cursor.execute("DEALLOCATE bulk_update_order")
cursor.close() cursor.close()

View File

@ -27,12 +27,19 @@ def _get_milestones_stats_for_backlog(project):
current_evolution = 0 current_evolution = 0
current_team_increment = 0 current_team_increment = 0
current_client_increment = 0 current_client_increment = 0
optimal_points_per_sprint = project.total_story_points / (project.total_milestones)
optimal_points_per_sprint = 0
if project.total_story_points and project.total_milestones:
optimal_points_per_sprint = project.total_story_points / project.total_milestones
future_team_increment = sum(project.future_team_increment.values()) future_team_increment = sum(project.future_team_increment.values())
future_client_increment = sum(project.future_client_increment.values()) future_client_increment = sum(project.future_client_increment.values())
milestones = project.milestones.order_by('estimated_start') milestones = project.milestones.order_by('estimated_start')
optimal_points = 0
team_increment = 0
client_increment = 0
for current_milestone in range(0, max(milestones.count(), project.total_milestones)): for current_milestone in range(0, max(milestones.count(), project.total_milestones)):
optimal_points = (project.total_story_points - optimal_points = (project.total_story_points -
(optimal_points_per_sprint * current_milestone)) (optimal_points_per_sprint * current_milestone))
@ -65,7 +72,7 @@ def _get_milestones_stats_for_backlog(project):
optimal_points -= optimal_points_per_sprint optimal_points -= optimal_points_per_sprint
evolution = (project.total_story_points - current_evolution evolution = (project.total_story_points - current_evolution
if current_evolution is not None else None) if current_evolution is not None and project.total_story_points else None)
yield { yield {
'name': 'Project End', 'name': 'Project End',
'optimal': optimal_points, 'optimal': optimal_points,

View File

@ -44,8 +44,8 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
model = models.Task model = models.Task
serializer_class = serializers.TaskNeighborsSerializer serializer_class = serializers.TaskNeighborsSerializer
list_serializer_class = serializers.TaskSerializer list_serializer_class = serializers.TaskSerializer
permission_classes = (IsAuthenticated, permissions.TaskPermission) permission_classes = (permissions.TaskPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewTasksFilterBackend,)
filter_fields = ["user_story", "milestone", "project"] filter_fields = ["user_story", "milestone", "project"]
def pre_save(self, obj): def pre_save(self, obj):
@ -58,10 +58,6 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
def pre_conditions_on_save(self, obj): def pre_conditions_on_save(self, obj):
super().pre_conditions_on_save(obj) super().pre_conditions_on_save(obj)
if (obj.project.owner != self.request.user and
obj.project.memberships.filter(user=self.request.user).count() == 0):
raise exc.PermissionDenied(_("You don't have permissions for add/modify this task."))
if obj.milestone and obj.milestone.project != obj.project: if obj.milestone and obj.milestone.project != obj.project:
raise exc.PermissionDenied(_("You don't have permissions for add/modify this task.")) raise exc.PermissionDenied(_("You don't have permissions for add/modify this task."))
@ -88,8 +84,7 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
project = get_object_or_404(Project, id=project_id) project = get_object_or_404(Project, id=project_id)
user_story = get_object_or_404(UserStory, id=us_id) user_story = get_object_or_404(UserStory, id=us_id)
if request.user != project.owner and not has_project_perm(request.user, project, 'add_task'): self.check_permissions(request, 'bulk_create', project)
raise exc.PermissionDenied(_("You don't have permisions to create tasks."))
tasks = services.create_tasks_in_bulk(bulk_tasks, callback=self.post_save, project=project, tasks = services.create_tasks_in_bulk(bulk_tasks, callback=self.post_save, project=project,
user_story=user_story, owner=request.user, user_story=user_story, owner=request.user,

View File

@ -14,14 +14,16 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.permissions import BasePermission from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny)
class TaskPermission(BasePermission): class TaskPermission(ResourcePermission):
get_permission = "view_task" enought_perms = IsProjectOwner()
post_permission = "add_task" global_perms = None
put_permission = "change_task" retrieve_perms = HasProjectPerm('view_tasks')
patch_permission = "change_task" create_perms = HasProjectPerm('add_task')
delete_permission = "delete_task" update_perms = HasProjectPerm('modify_task')
safe_methods = ["HEAD", "OPTIONS"] destroy_perms = HasProjectPerm('delete_task')
path_to_project = ["project"] list_perms = AllowAny()
bulk_create_perms = HasProjectPerm('add_task')

View File

@ -46,9 +46,9 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
model = models.UserStory model = models.UserStory
serializer_class = serializers.UserStoryNeighborsSerializer serializer_class = serializers.UserStoryNeighborsSerializer
list_serializer_class = serializers.UserStorySerializer list_serializer_class = serializers.UserStorySerializer
permission_classes = (IsAuthenticated, permissions.UserStoryPermission) permission_classes = (permissions.UserStoryPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend, filters.TagsFilter) filter_backends = (filters.CanViewUsFilterBackend, filters.TagsFilter)
retrieve_exclude_filters = (filters.TagsFilter,) retrieve_exclude_filters = (filters.TagsFilter,)
filter_fields = ['project', 'milestone', 'milestone__isnull', 'status', 'is_archived'] filter_fields = ['project', 'milestone', 'milestone__isnull', 'status', 'is_archived']
@ -76,8 +76,7 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
project = get_object_or_404(Project, id=project_id) project = get_object_or_404(Project, id=project_id)
if request.user != project.owner and not has_project_perm(request.user, project, 'add_userstory'): self.check_permissions(request, 'bulk_create', project)
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
user_stories = services.create_userstories_in_bulk( user_stories = services.create_userstories_in_bulk(
bulk_stories, callback=self.post_save, project=project, owner=request.user) bulk_stories, callback=self.post_save, project=project, owner=request.user)
@ -103,8 +102,7 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
project = get_object_or_404(Project, id=project_id) project = get_object_or_404(Project, id=project_id)
if request.user != project.owner and not has_project_perm(request.user, project, 'change_userstory'): self.check_permissions(request, 'bulk_update_order', project)
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
services.update_userstories_order_in_bulk(bulk_stories) services.update_userstories_order_in_bulk(bulk_stories)
@ -134,16 +132,3 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
obj.owner = self.request.user obj.owner = self.request.user
super().pre_save(obj) super().pre_save(obj)
def pre_conditions_on_save(self, obj):
super().pre_conditions_on_save(obj)
if (obj.project.owner != self.request.user and
obj.project.memberships.filter(user=self.request.user).count() == 0):
raise exc.PermissionDenied(_("You don't have permissions for add/modify this user story"))
if obj.milestone and obj.milestone.project != obj.project:
raise exc.PermissionDenied(_("You don't have permissions for add/modify this user story"))
if obj.status and obj.status.project != obj.project:
raise exc.PermissionDenied(_("You don't have permissions for add/modify this user story"))

View File

@ -14,14 +14,16 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.permissions import BasePermission from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsAuthenticated, IsProjectOwner,
AllowAny, IsSuperUser)
class UserStoryPermission(BasePermission): class UserStoryPermission(ResourcePermission):
get_permission = "view_userstory" retrieve_perms = HasProjectPerm('view_us')
post_permission = "add_userstory" create_perms = HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us')
put_permission = "change_userstory" update_perms = HasProjectPerm('modify_us')
patch_permission = "change_userstory" destroy_perms = HasProjectPerm('delete_us')
delete_permission = "delete_userstory" list_perms = AllowAny()
safe_methods = ["HEAD", "OPTIONS"] bulk_create_perms = IsAuthenticated() & (HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us'))
path_to_project = ["project"] bulk_update_order_perms = HasProjectPerm('modify_us')

View File

@ -55,7 +55,6 @@ class UserStorySerializer(serializers.ModelSerializer):
points_modelcls = get_model("projects", "Points") points_modelcls = get_model("projects", "Points")
obj.project.update_role_points()
if role_points: if role_points:
for role_id, points_id in role_points.items(): for role_id, points_id in role_points.items():
role_points = obj.role_points.get(role__id=role_id) role_points = obj.role_points.get(role__id=role_id)

View File

@ -32,6 +32,12 @@ class Votes(models.Model):
verbose_name_plural = _("Votes") verbose_name_plural = _("Votes")
unique_together = ("content_type", "object_id") unique_together = ("content_type", "object_id")
@property
def project(self):
if hasattr(self.content_object, 'project'):
return self.content_object.project
return None
def __str__(self): def __str__(self):
return self.count return self.count
@ -48,5 +54,11 @@ class Vote(models.Model):
verbose_name_plural = _("Votes") verbose_name_plural = _("Votes")
unique_together = ("content_type", "object_id", "user") unique_together = ("content_type", "object_id", "user")
@property
def project(self):
if hasattr(self.content_object, 'project'):
return self.content_object.project
return None
def __str__(self): def __str__(self):
return self.user return self.user

View File

@ -34,7 +34,7 @@ def add_vote(obj, user):
""" """
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj) obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
with atomic(): with atomic():
_, created = Vote.objects.get_or_create(content_type=obj_type, object_id=obj.id, user=user) vote, created = Vote.objects.get_or_create(content_type=obj_type, object_id=obj.id, user=user)
if not created: if not created:
return return
@ -42,6 +42,7 @@ def add_vote(obj, user):
votes, _ = Votes.objects.get_or_create(content_type=obj_type, object_id=obj.id) votes, _ = Votes.objects.get_or_create(content_type=obj_type, object_id=obj.id)
votes.count = F('count') + 1 votes.count = F('count') + 1
votes.save() votes.save()
return vote
def remove_vote(obj, user): def remove_vote(obj, user):
@ -74,7 +75,6 @@ def get_voters(obj):
:return: User queryset object representing the users that voted the object. :return: User queryset object representing the users that voted the object.
""" """
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj) obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
return get_user_model().objects.filter(votes__content_type=obj_type, votes__object_id=obj.id) return get_user_model().objects.filter(votes__content_type=obj_type, votes__object_id=obj.id)

View File

@ -41,8 +41,8 @@ from . import serializers
class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet): class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
model = models.WikiPage model = models.WikiPage
serializer_class = serializers.WikiPageSerializer serializer_class = serializers.WikiPageSerializer
permission_classes = (IsAuthenticated,) permission_classes = (permissions.WikiPagePermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewWikiPagesFilterBackend,)
filter_fields = ("project", "slug") filter_fields = ("project", "slug")
@list_route(methods=["POST"]) @list_route(methods=["POST"])
@ -57,16 +57,12 @@ class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
raise exc.WrongArguments({"project_id": "No project_id parameter"}) raise exc.WrongArguments({"project_id": "No project_id parameter"})
project = get_object_or_404(Project, pk=project_id) project = get_object_or_404(Project, pk=project_id)
self.check_permissions(request, "render", project)
data = mdrender(project, content) data = mdrender(project, content)
return Response({"data": data}) return Response({"data": data})
def pre_conditions_on_save(self, obj):
super().pre_conditions_on_save(obj)
if (obj.project.owner != self.request.user and
obj.project.memberships.filter(user=self.request.user).count() == 0):
raise exc.PermissionDenied(_("You don't haver permissions for add/modify "
"this wiki page."))
def pre_save(self, obj): def pre_save(self, obj):
if not obj.owner: if not obj.owner:
obj.owner = self.request.user obj.owner = self.request.user
@ -77,6 +73,6 @@ class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
class WikiLinkViewSet(ModelCrudViewSet): class WikiLinkViewSet(ModelCrudViewSet):
model = models.WikiLink model = models.WikiLink
serializer_class = serializers.WikiLinkSerializer serializer_class = serializers.WikiLinkSerializer
permission_classes = (IsAuthenticated,) permission_classes = (permissions.WikiLinkPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.CanViewWikiPagesFilterBackend,)
filter_fields = ["project"] filter_fields = ["project"]

View File

@ -14,14 +14,25 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.permissions import BasePermission from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny)
class WikiPagePermission(BasePermission): class WikiPagePermission(ResourcePermission):
get_permission = "view_wikipage" enought_perms = IsProjectOwner()
post_permission = "add_wikipage" global_perms = None
put_permission = "change_wikipage" retrieve_perms = HasProjectPerm('view_wiki_pages')
patch_permission = "change_wikipage" create_perms = HasProjectPerm('add_wiki_page')
delete_permission = "delete_wikipage" update_perms = HasProjectPerm('modify_wiki_page')
safe_methods = ["HEAD", "OPTIONS"] destroy_perms = HasProjectPerm('delete_wiki_page')
path_to_project = ["project"] list_perms = AllowAny()
render_perms = AllowAny()
class WikiLinkPermission(ResourcePermission):
enought_perms = IsProjectOwner()
global_perms = None
retrieve_perms = HasProjectPerm('view_wiki_links')
create_perms = HasProjectPerm('add_wiki_link')
update_perms = HasProjectPerm('modify_wiki_link')
destroy_perms = HasProjectPerm('delete_wiki_link')
list_perms = AllowAny()

View File

@ -44,10 +44,6 @@ from taiga.searches.api import SearchViewSet
router.register(r"search", SearchViewSet, base_name="search") router.register(r"search", SearchViewSet, base_name="search")
from taiga.projects.api import ProjectAdminViewSet
router.register(r"site-projects", ProjectAdminViewSet, base_name="site-projects")
# Projects & Types # Projects & Types
from taiga.projects.api import RolesViewSet from taiga.projects.api import RolesViewSet
from taiga.projects.api import ProjectViewSet from taiga.projects.api import ProjectViewSet
@ -61,13 +57,9 @@ from taiga.projects.api import IssueTypeViewSet
from taiga.projects.api import PriorityViewSet from taiga.projects.api import PriorityViewSet
from taiga.projects.api import SeverityViewSet from taiga.projects.api import SeverityViewSet
from taiga.projects.api import ProjectTemplateViewSet from taiga.projects.api import ProjectTemplateViewSet
from taiga.projects.api import FansViewSet
from taiga.projects.api import StarredViewSet
router.register(r"roles", RolesViewSet, base_name="roles") router.register(r"roles", RolesViewSet, base_name="roles")
router.register(r"projects", ProjectViewSet, base_name="projects") router.register(r"projects", ProjectViewSet, base_name="projects")
router.register(r"projects/(?P<project_id>\d+)/fans", FansViewSet, base_name="project-fans")
router.register(r"users/(?P<user_id>\d+)/starred", StarredViewSet, base_name="user-starred")
router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates") router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates")
router.register(r"memberships", MembershipViewSet, base_name="memberships") router.register(r"memberships", MembershipViewSet, base_name="memberships")
router.register(r"invitations", InvitationViewSet, base_name="invitations") router.register(r"invitations", InvitationViewSet, base_name="invitations")

View File

@ -24,6 +24,7 @@ from taiga.projects.userstories.serializers import UserStorySerializer
from taiga.projects.tasks.serializers import TaskSerializer from taiga.projects.tasks.serializers import TaskSerializer
from taiga.projects.issues.serializers import IssueSerializer from taiga.projects.issues.serializers import IssueSerializer
from taiga.projects.wiki.serializers import WikiPageSerializer from taiga.projects.wiki.serializers import WikiPageSerializer
from taiga.permissions.service import user_has_perm
from . import services from . import services
@ -40,22 +41,22 @@ class SearchViewSet(viewsets.ViewSet):
except (project_model.DoesNotExist, TypeError): except (project_model.DoesNotExist, TypeError):
raise excp.PermissionDenied({"detail": "Wrong project id"}) raise excp.PermissionDenied({"detail": "Wrong project id"})
result = { result = {}
"userstories": self._search_user_stories(project, text), if user_has_perm(request.user, "view_us", project):
"tasks": self._search_tasks(project, text), result["userstories"] = self._search_user_stories(project, text)
"issues": self._search_issues(project, text), if user_has_perm(request.user, "view_tasks", project):
"wikipages": self._search_wiki_pages(project, text) result["tasks"] = self._search_tasks(project, text)
} if user_has_perm(request.user, "view_issues", project):
result["issues"] = self._search_issues(project, text)
if user_has_perm(request.user, "view_wiki_pages", project):
result["wikipages"] = self._search_wiki_pages(project, text)
result["count"] = sum(map(lambda x: len(x), result.values())) result["count"] = sum(map(lambda x: len(x), result.values()))
return Response(result) return Response(result)
def _get_project(self, project_id): def _get_project(self, project_id):
project_model = get_model("projects", "Project") project_model = get_model("projects", "Project")
own_projects = (project_model.objects return project_model.objects.get(pk=project_id)
.filter(members=self.request.user))
return own_projects.get(pk=project_id)
def _search_user_stories(self, project, text): def _search_user_stories(self, project, text):
queryset = services.search_user_stories(project, text) queryset = services.search_user_stories(project, text)

View File

@ -23,8 +23,8 @@ from taiga.base.api import GenericViewSet
from . import serializers from . import serializers
from . import service from . import service
from . import permissions
# TODO: Set Timelines permissions from . import models
class TimelineViewSet(GenericViewSet): class TimelineViewSet(GenericViewSet):
@ -36,13 +36,13 @@ class TimelineViewSet(GenericViewSet):
app_name, model = self.content_type.split(".", 1) app_name, model = self.content_type.split(".", 1)
return get_object_or_404(ContentType, app_label=app_name, model=model) return get_object_or_404(ContentType, app_label=app_name, model=model)
def get_object(self): def get_queryset(self):
ct = self.get_content_type() ct = self.get_content_type()
model_cls = ct.model_class() model_cls = ct.model_class()
qs = model_cls.objects.all() qs = model_cls.objects.all()
filtered_qs = self.filter_queryset(qs) filtered_qs = self.filter_queryset(qs)
return super().get_object(queryset=filtered_qs) return filtered_qs
def response_for_queryset(self, queryset): def response_for_queryset(self, queryset):
# Switch between paginated or standard style responses # Switch between paginated or standard style responses
@ -61,13 +61,17 @@ class TimelineViewSet(GenericViewSet):
def retrieve(self, request, pk): def retrieve(self, request, pk):
obj = self.get_object() obj = self.get_object()
self.check_permissions(request, "retrieve", obj)
qs = service.get_timeline(obj) qs = service.get_timeline(obj)
return self.response_for_queryset(qs) return self.response_for_queryset(qs)
class UserTimeline(TimelineViewSet): class UserTimeline(TimelineViewSet):
content_type = "users.user" content_type = "users.user"
permission_classes = (permissions.UserTimelinePermission,)
class ProjectTimeline(TimelineViewSet): class ProjectTimeline(TimelineViewSet):
content_type = "projects.project" content_type = "projects.project"
permission_classes = (permissions.ProjectTimelinePermission,)

View File

@ -0,0 +1,26 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny)
class UserTimelinePermission(ResourcePermission):
retrieve_perms = AllowAny()
class ProjectTimelinePermission(ResourcePermission):
retrieve_perms = HasProjectPerm('view_project')

View File

@ -20,7 +20,7 @@ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.contrib import admin from django.contrib import admin
from .routers import router from .routers import router
from .projects.attachments.views import RawAttachmentView from .projects.attachments.api import RawAttachmentView

View File

@ -41,7 +41,7 @@ class RoleAdmin(admin.ModelAdmin):
db_field, request=request, **kwargs) db_field, request=request, **kwargs)
admin.site.register(Role, RoleAdmin) # admin.site.register(Role, RoleAdmin)
class UserAdmin(DjangoUserAdmin): class UserAdmin(DjangoUserAdmin):

View File

@ -25,16 +25,21 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.filters import BaseFilterBackend from rest_framework.filters import BaseFilterBackend
from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework import status, viewsets from rest_framework import status
from djmail.template_mail import MagicMailBuilder from djmail.template_mail import MagicMailBuilder
from taiga.base.decorators import list_route, action from taiga.base.decorators import list_route, detail_route
from taiga.base.decorators import action
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base.api import ModelCrudViewSet, RetrieveModelMixin, ModelListViewSet from taiga.base.api import ModelCrudViewSet
from taiga.base.api import ModelListViewSet
from taiga.projects.votes import services as votes_service
from taiga.projects.serializers import StarredSerializer
from .models import User, Role from . import models
from .serializers import UserSerializer, RecoverySerializer from . import serializers
from . import permissions
class MembersFilterBackend(BaseFilterBackend): class MembersFilterBackend(BaseFilterBackend):
@ -55,31 +60,45 @@ class MembersFilterBackend(BaseFilterBackend):
class UsersViewSet(ModelCrudViewSet): class UsersViewSet(ModelCrudViewSet):
permission_classes = (IsAuthenticated,) permission_classes = (permissions.UserPermission,)
serializer_class = UserSerializer serializer_class = serializers.UserSerializer
queryset = User.objects.all() queryset = models.User.objects.all()
filter_backends = (MembersFilterBackend,)
def pre_conditions_on_save(self, obj): def pre_conditions_on_save(self, obj):
if not self.request.user.is_superuser and obj.id != self.request.user.id: if self.request.user.is_superuser:
return
if obj.id == self.request.user.id:
return
if obj.id is None:
return
raise exc.PreconditionError() raise exc.PreconditionError()
def pre_conditions_on_delete(self, obj): def pre_conditions_on_delete(self, obj):
if not self.request.user.is_superuser and obj.id != self.request.user.id: if self.request.user.is_superuser:
return
if obj.id == self.request.user.id:
return
raise exc.PreconditionError() raise exc.PreconditionError()
@list_route(permission_classes=[AllowAny], methods=["POST"]) @list_route(methods=["POST"])
def password_recovery(self, request, pk=None): def password_recovery(self, request, pk=None):
username_or_email = request.DATA.get('username', None) username_or_email = request.DATA.get('username', None)
self.check_permissions(request, "password_recovery", None)
if not username_or_email: if not username_or_email:
raise exc.WrongArguments(_("Invalid username or email")) raise exc.WrongArguments(_("Invalid username or email"))
try: try:
queryset = User.objects.all() queryset = models.User.objects.all()
user = queryset.get(Q(username=username_or_email) | user = queryset.get(Q(username=username_or_email) |
Q(email=username_or_email)) Q(email=username_or_email))
except User.DoesNotExist: except models.User.DoesNotExist:
raise exc.WrongArguments(_("Invalid username or email")) raise exc.WrongArguments(_("Invalid username or email"))
user.token = str(uuid.uuid1()) user.token = str(uuid.uuid1())
@ -92,27 +111,36 @@ class UsersViewSet(ModelCrudViewSet):
return Response({"detail": _("Mail sended successful!"), return Response({"detail": _("Mail sended successful!"),
"email": user.email}) "email": user.email})
@list_route(permission_classes=[AllowAny], methods=["POST"]) @list_route(methods=["POST"])
def change_password_from_recovery(self, request, pk=None): def change_password_from_recovery(self, request, pk=None):
""" """
Change password with token (from password recovery step). Change password with token (from password recovery step).
""" """
serializer = RecoverySerializer(data=request.DATA, many=False)
self.check_permissions(request, "change_password_from_recovery", None)
serializer = serializers.RecoverySerializer(data=request.DATA, many=False)
if not serializer.is_valid(): if not serializer.is_valid():
raise exc.WrongArguments(_("Token is invalid")) raise exc.WrongArguments(_("Token is invalid"))
user = User.objects.get(token=serializer.data["token"]) try:
user = models.User.objects.get(token=serializer.data["token"])
except models.User.DoesNotExist:
raise exc.WrongArguments(_("Token is invalid"))
user.set_password(serializer.data["password"]) user.set_password(serializer.data["password"])
user.token = None user.token = None
user.save(update_fields=["password", "token"]) user.save(update_fields=["password", "token"])
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@list_route(permission_classes=[IsAuthenticated], methods=["POST"]) @list_route(methods=["POST"])
def change_password(self, request, pk=None): def change_password(self, request, pk=None):
""" """
Change password to current logged user. Change password to current logged user.
""" """
self.check_permissions(request, "change_password", None)
password = request.DATA.get("password") password = request.DATA.get("password")
if not password: if not password:
@ -124,3 +152,12 @@ class UsersViewSet(ModelCrudViewSet):
request.user.set_password(password) request.user.set_password(password)
request.user.save(update_fields=["password"]) request.user.save(update_fields=["password"])
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@detail_route(methods=["GET"])
def starred(self, request, pk=None):
user = self.get_object()
self.check_permissions(request, 'starred', user)
stars = votes_service.get_voted(user.pk, model=get_model('projects', 'Project'))
stars_data = StarredSerializer(stars, many=True)
return Response(stars_data.data)

View File

@ -21,7 +21,10 @@ from django.contrib.auth.models import UserManager, AbstractBaseUser
from django.core import validators from django.core import validators
from django.utils import timezone from django.utils import timezone
from djorm_pgarray.fields import TextArrayField
from taiga.base.utils.slug import slugify_uniquely from taiga.base.utils.slug import slugify_uniquely
from taiga.permissions.permissions import MEMBERS_PERMISSIONS
import random import random
import re import re
@ -124,8 +127,10 @@ class Role(models.Model):
verbose_name=_("name")) verbose_name=_("name"))
slug = models.SlugField(max_length=250, null=False, blank=True, slug = models.SlugField(max_length=250, null=False, blank=True,
verbose_name=_("slug")) verbose_name=_("slug"))
permissions = models.ManyToManyField("auth.Permission", related_name="roles", permissions = TextArrayField(blank=True, null=True,
verbose_name=_("permissions")) default=[],
verbose_name=_("permissions"),
choices=MEMBERS_PERMISSIONS)
order = models.IntegerField(default=10, null=False, blank=False, order = models.IntegerField(default=10, null=False, blank=False,
verbose_name=_("order")) verbose_name=_("order"))
project = models.ForeignKey("projects.Project", null=False, blank=False, project = models.ForeignKey("projects.Project", null=False, blank=False,

View File

@ -0,0 +1,38 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import (ResourcePermission, IsSuperUser,
AllowAny, PermissionComponent,
IsAuthenticated)
class IsTheSameUser(PermissionComponent):
def check_permissions(self, request, view, obj=None):
return request.user.is_authenticated() and request.user.pk == obj.pk
class UserPermission(ResourcePermission):
enought_perms = IsSuperUser()
global_perms = None
retrieve_perms = AllowAny()
create_perms = AllowAny()
update_perms = IsTheSameUser()
destroy_perms = IsTheSameUser()
list_perms = AllowAny()
password_recovery_perms = AllowAny()
change_password_from_recovery_perms = AllowAny()
change_password_perms = IsAuthenticated()
starred_perms = AllowAny()

View File

@ -42,12 +42,3 @@ class UserSerializer(serializers.ModelSerializer):
class RecoverySerializer(serializers.Serializer): class RecoverySerializer(serializers.Serializer):
token = serializers.CharField(max_length=200) token = serializers.CharField(max_length=200)
password = serializers.CharField(min_length=6) password = serializers.CharField(min_length=6)
def validate_token(self, attrs, source):
token = attrs[source]
try:
user = User.objects.get(token=token)
except User.DoesNotExist:
raise serializers.ValidationError(_("invalid token"))
return attrs

View File

@ -32,13 +32,16 @@ class StorageEntriesViewSet(ModelCrudViewSet):
model = models.StorageEntry model = models.StorageEntry
filter_backends = (filters.StorageEntriesFilterBackend,) filter_backends = (filters.StorageEntriesFilterBackend,)
serializer_class = serializers.StorageEntrySerializer serializer_class = serializers.StorageEntrySerializer
permission_classes = (IsAuthenticated, permissions.StorageEntriesPermission) permission_classes = [permissions.StorageEntriesPermission]
lookup_field = "key" lookup_field = "key"
def get_queryset(self): def get_queryset(self):
if self.request.user.is_anonymous():
return self.model.objects.none()
return self.request.user.storage_entries.all() return self.request.user.storage_entries.all()
def pre_save(self, obj): def pre_save(self, obj):
if self.request.user.is_authenticated():
obj.owner = self.request.user obj.owner = self.request.user
def create(self, *args, **kwargs): def create(self, *args, **kwargs):

View File

@ -14,9 +14,9 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.permissions import Permission from taiga.base.api.permissions import ResourcePermission, IsAuthenticated, DenyAll
class StorageEntriesPermission(Permission): class StorageEntriesPermission(ResourcePermission):
def has_object_permission(self, request, view, obj): enought_perms = IsAuthenticated()
return request.user == obj.owner global_perms = DenyAll()

View File

@ -44,6 +44,7 @@ class ProjectTemplateFactory(Factory):
name = "Template name" name = "Template name"
slug = settings.DEFAULT_PROJECT_TEMPLATE slug = settings.DEFAULT_PROJECT_TEMPLATE
description = factory.Sequence(lambda n: "Description {}".format(n))
us_statuses = [] us_statuses = []
points = [] points = []
@ -80,6 +81,28 @@ class PointsFactory(Factory):
project = factory.SubFactory("tests.factories.ProjectFactory") project = factory.SubFactory("tests.factories.ProjectFactory")
class AttachmentFactory(Factory):
FACTORY_FOR = get_model("attachments", "Attachment")
project = factory.SubFactory("tests.factories.ProjectFactory")
owner = factory.SubFactory("tests.factories.UserFactory")
class UserStoryAttachmentFactory(AttachmentFactory):
content_object = factory.SubFactory("tests.factories.UserStoryFactory")
class TaskAttachmentFactory(AttachmentFactory):
content_object = factory.SubFactory("tests.factories.TaskFactory")
class IssueAttachmentFactory(AttachmentFactory):
content_object = factory.SubFactory("tests.factories.IssueFactory")
class WikiAttachmentFactory(AttachmentFactory):
content_object = factory.SubFactory("tests.factories.WikiFactory")
class RolePointsFactory(Factory): class RolePointsFactory(Factory):
FACTORY_FOR = get_model("userstories", "RolePoints") FACTORY_FOR = get_model("userstories", "RolePoints")
@ -131,17 +154,6 @@ class UserStoryStatusFactory(Factory):
project = factory.SubFactory("tests.factories.ProjectFactory") project = factory.SubFactory("tests.factories.ProjectFactory")
class TaskFactory(Factory):
FACTORY_FOR = get_model("tasks", "Task")
ref = factory.Sequence(lambda n: n)
owner = factory.SubFactory("tests.factories.UserFactory")
subject = factory.Sequence(lambda n: "Task {}".format(n))
user_story = factory.SubFactory("tests.factories.UserStoryFactory")
status = factory.SubFactory("tests.factories.TaskStatusFactory")
project = factory.SubFactory("tests.factories.ProjectFactory")
milestone = factory.SubFactory("tests.factories.MilestoneFactory")
class TaskStatusFactory(Factory): class TaskStatusFactory(Factory):
FACTORY_FOR = get_model("projects", "TaskStatus") FACTORY_FOR = get_model("projects", "TaskStatus")
@ -177,12 +189,14 @@ class IssueFactory(Factory):
class TaskFactory(Factory): class TaskFactory(Factory):
FACTORY_FOR = get_model("tasks", "Task") FACTORY_FOR = get_model("tasks", "Task")
ref = factory.Sequence(lambda n: n)
subject = factory.Sequence(lambda n: "Task {}".format(n)) subject = factory.Sequence(lambda n: "Task {}".format(n))
description = factory.Sequence(lambda n: "Task {} description".format(n)) description = factory.Sequence(lambda n: "Task {} description".format(n))
owner = factory.SubFactory("tests.factories.UserFactory") owner = factory.SubFactory("tests.factories.UserFactory")
project = factory.SubFactory("tests.factories.ProjectFactory") project = factory.SubFactory("tests.factories.ProjectFactory")
status = factory.SubFactory("tests.factories.TaskStatusFactory") status = factory.SubFactory("tests.factories.TaskStatusFactory")
milestone = factory.SubFactory("tests.factories.MilestoneFactory") milestone = factory.SubFactory("tests.factories.MilestoneFactory")
user_story = factory.SubFactory("tests.factories.UserStoryFactory")
class WikiPageFactory(Factory): class WikiPageFactory(Factory):
@ -194,6 +208,15 @@ class WikiPageFactory(Factory):
content = factory.Sequence(lambda n: "Wiki Page {} content".format(n)) content = factory.Sequence(lambda n: "Wiki Page {} content".format(n))
class WikiLinkFactory(Factory):
FACTORY_FOR = get_model("wiki", "WikiLink")
project = factory.SubFactory("tests.factories.ProjectFactory")
title = factory.Sequence(lambda n: "Wiki Link {} title".format(n))
href = factory.Sequence(lambda n: "link-{}".format(n))
order = factory.Sequence(lambda n: n)
class IssueStatusFactory(Factory): class IssueStatusFactory(Factory):
FACTORY_FOR = get_model("projects", "IssueStatus") FACTORY_FOR = get_model("projects", "IssueStatus")
@ -201,10 +224,10 @@ class IssueStatusFactory(Factory):
project = factory.SubFactory("tests.factories.ProjectFactory") project = factory.SubFactory("tests.factories.ProjectFactory")
class TaskStatusFactory(Factory): class UserStoryStatusFactory(Factory):
FACTORY_FOR = get_model("projects", "TaskStatus") FACTORY_FOR = get_model("projects", "UserStoryStatus")
name = factory.Sequence(lambda n: "Issue Status {}".format(n)) name = factory.Sequence(lambda n: "User Story Status {}".format(n))
project = factory.SubFactory("tests.factories.ProjectFactory") project = factory.SubFactory("tests.factories.ProjectFactory")

View File

@ -0,0 +1,594 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from taiga.projects.attachments.serializers import AttachmentSerializer
from tests import factories as f
from tests.utils import helper_test_http_method, helper_test_http_method_and_count, disconnect_signals, reconnect_signals
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
m.public_user_story = f.UserStoryFactory(project=m.public_project, ref=1)
m.public_task = f.TaskFactory(project=m.public_project, ref=2)
m.public_issue = f.IssueFactory(project=m.public_project, ref=3)
m.public_wiki = f.WikiPageFactory(project=m.public_project, slug=4)
m.public_user_story_attachment = f.UserStoryAttachmentFactory(project=m.public_project, content_object=m.public_user_story)
m.public_task_attachment = f.TaskAttachmentFactory(project=m.public_project, content_object=m.public_task)
m.public_issue_attachment = f.IssueAttachmentFactory(project=m.public_project, content_object=m.public_issue)
m.public_wiki_attachment = f.WikiAttachmentFactory(project=m.public_project, content_object=m.public_wiki)
m.private_user_story1 = f.UserStoryFactory(project=m.private_project1, ref=5)
m.private_task1 = f.TaskFactory(project=m.private_project1, ref=6)
m.private_issue1 = f.IssueFactory(project=m.private_project1, ref=7)
m.private_wiki1 = f.WikiPageFactory(project=m.private_project1, slug=8)
m.private_user_story1_attachment = f.UserStoryAttachmentFactory(project=m.private_project1, content_object=m.private_user_story1)
m.private_task1_attachment = f.TaskAttachmentFactory(project=m.private_project1, content_object=m.private_task1)
m.private_issue1_attachment = f.IssueAttachmentFactory(project=m.private_project1, content_object=m.private_issue1)
m.private_wiki1_attachment = f.WikiAttachmentFactory(project=m.private_project1, content_object=m.private_wiki1)
m.private_user_story2 = f.UserStoryFactory(project=m.private_project2, ref=9)
m.private_task2 = f.TaskFactory(project=m.private_project2, ref=10)
m.private_issue2 = f.IssueFactory(project=m.private_project2, ref=11)
m.private_wiki2 = f.WikiPageFactory(project=m.private_project2, slug=12)
m.private_user_story2_attachment = f.UserStoryAttachmentFactory(project=m.private_project2, content_object=m.private_user_story2)
m.private_task2_attachment = f.TaskAttachmentFactory(project=m.private_project2, content_object=m.private_task2)
m.private_issue2_attachment = f.IssueAttachmentFactory(project=m.private_project2, content_object=m.private_issue2)
m.private_wiki2_attachment = f.WikiAttachmentFactory(project=m.private_project2, content_object=m.private_wiki2)
return m
def test_user_story_attachment_retrieve(client, data):
public_url = reverse('userstory-attachments-detail', kwargs={"pk": data.public_user_story_attachment.pk})
private_url1 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story1_attachment.pk})
private_url2 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_task_attachment_retrieve(client, data):
public_url = reverse('task-attachments-detail', kwargs={"pk": data.public_task_attachment.pk})
private_url1 = reverse('task-attachments-detail', kwargs={"pk": data.private_task1_attachment.pk})
private_url2 = reverse('task-attachments-detail', kwargs={"pk": data.private_task2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_issue_attachment_retrieve(client, data):
public_url = reverse('issue-attachments-detail', kwargs={"pk": data.public_issue_attachment.pk})
private_url1 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue1_attachment.pk})
private_url2 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_wiki_attachment_retrieve(client, data):
public_url = reverse('wiki-attachments-detail', kwargs={"pk": data.public_wiki_attachment.pk})
private_url1 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki1_attachment.pk})
private_url2 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_user_story_attachment_update(client, data):
public_url = reverse('userstory-attachments-detail', kwargs={"pk": data.public_user_story_attachment.pk})
private_url1 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story1_attachment.pk})
private_url2 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = AttachmentSerializer(data.public_user_story_attachment).data
attachment_data["description"] = "test"
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'put', public_url, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'put', private_url1, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'put', private_url2, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
def test_task_attachment_update(client, data):
public_url = reverse('task-attachments-detail', kwargs={"pk": data.public_task_attachment.pk})
private_url1 = reverse('task-attachments-detail', kwargs={"pk": data.private_task1_attachment.pk})
private_url2 = reverse('task-attachments-detail', kwargs={"pk": data.private_task2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = AttachmentSerializer(data.public_task_attachment).data
attachment_data["description"] = "test"
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'put', public_url, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'put', private_url1, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'put', private_url2, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
def test_issue_attachment_update(client, data):
public_url = reverse('issue-attachments-detail', kwargs={"pk": data.public_issue_attachment.pk})
private_url1 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue1_attachment.pk})
private_url2 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = AttachmentSerializer(data.public_issue_attachment).data
attachment_data["description"] = "test"
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'put', public_url, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'put', private_url1, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'put', private_url2, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
def test_wiki_attachment_update(client, data):
public_url = reverse('wiki-attachments-detail', kwargs={"pk": data.public_wiki_attachment.pk})
private_url1 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki1_attachment.pk})
private_url2 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = AttachmentSerializer(data.public_wiki_attachment).data
attachment_data["description"] = "test"
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'put', public_url, attachment_data, users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'put', private_url1, attachment_data, users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'put', private_url2, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
def test_user_story_attachment_patch(client, data):
public_url = reverse('userstory-attachments-detail', kwargs={"pk": data.public_user_story_attachment.pk})
private_url1 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story1_attachment.pk})
private_url2 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = {"description": "test"}
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'patch', public_url, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'patch', private_url1, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'patch', private_url2, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
def test_task_attachment_patch(client, data):
public_url = reverse('task-attachments-detail', kwargs={"pk": data.public_task_attachment.pk})
private_url1 = reverse('task-attachments-detail', kwargs={"pk": data.private_task1_attachment.pk})
private_url2 = reverse('task-attachments-detail', kwargs={"pk": data.private_task2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = {"description": "test"}
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'patch', public_url, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'patch', private_url1, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'patch', private_url2, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
def test_issue_attachment_patch(client, data):
public_url = reverse('issue-attachments-detail', kwargs={"pk": data.public_issue_attachment.pk})
private_url1 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue1_attachment.pk})
private_url2 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = {"description": "test"}
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'patch', public_url, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'patch', private_url1, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'patch', private_url2, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
def test_wiki_attachment_patch(client, data):
public_url = reverse('wiki-attachments-detail', kwargs={"pk": data.public_wiki_attachment.pk})
private_url1 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki1_attachment.pk})
private_url2 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = {"description": "test"}
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'patch', public_url, attachment_data, users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'patch', private_url1, attachment_data, users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'patch', private_url2, attachment_data, users)
assert results == [401, 403, 403, 200, 200]
def test_user_story_attachment_delete(client, data):
public_url = reverse('userstory-attachments-detail', kwargs={"pk": data.public_user_story_attachment.pk})
private_url1 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story1_attachment.pk})
private_url2 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_task_attachment_delete(client, data):
public_url = reverse('task-attachments-detail', kwargs={"pk": data.public_task_attachment.pk})
private_url1 = reverse('task-attachments-detail', kwargs={"pk": data.private_task1_attachment.pk})
private_url2 = reverse('task-attachments-detail', kwargs={"pk": data.private_task2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_issue_attachment_delete(client, data):
public_url = reverse('issue-attachments-detail', kwargs={"pk": data.public_issue_attachment.pk})
private_url1 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue1_attachment.pk})
private_url2 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_wiki_attachment_delete(client, data):
public_url = reverse('wiki-attachments-detail', kwargs={"pk": data.public_wiki_attachment.pk})
private_url1 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki1_attachment.pk})
private_url2 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki2_attachment.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, [None, data.registered_user])
assert results == [401, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, [None, data.registered_user])
assert results == [401, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_user_story_attachment_create(client, data):
url = reverse('userstory-attachments-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = AttachmentSerializer(data.public_user_story_attachment).data
attachment_data["id"] = None
attachment_data["description"] = "test"
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'post', url, attachment_data, users)
assert results == [401, 403, 403, 201, 201]
def test_task_attachment_create(client, data):
url = reverse('task-attachments-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = AttachmentSerializer(data.public_task_attachment).data
attachment_data["id"] = None
attachment_data["description"] = "test"
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'post', url, attachment_data, users)
assert results == [401, 403, 403, 201, 201]
def test_issue_attachment_create(client, data):
url = reverse('issue-attachments-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = AttachmentSerializer(data.public_issue_attachment).data
attachment_data["id"] = None
attachment_data["description"] = "test"
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'post', url, attachment_data, users)
assert results == [401, 403, 403, 201, 201]
def test_wiki_attachment_create(client, data):
url = reverse('wiki-attachments-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
attachment_data = AttachmentSerializer(data.public_wiki_attachment).data
attachment_data["id"] = None
attachment_data["description"] = "test"
attachment_data = JSONRenderer().render(attachment_data)
results = helper_test_http_method(client, 'post', url, attachment_data, users)
assert results == [401, 201, 201, 201, 201]
def test_user_story_attachment_list(client, data):
url = reverse('userstory-attachments-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method_and_count(client, 'get', url, None, users)
assert results == [(200, 2), (200, 2), (200, 3), (200, 3), (200, 3)]
def test_task_attachment_list(client, data):
url = reverse('task-attachments-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method_and_count(client, 'get', url, None, users)
assert results == [(200, 2), (200, 2), (200, 3), (200, 3), (200, 3)]
def test_issue_attachment_list(client, data):
url = reverse('issue-attachments-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method_and_count(client, 'get', url, None, users)
assert results == [(200, 2), (200, 2), (200, 3), (200, 3), (200, 3)]
def test_wiki_attachment_list(client, data):
url = reverse('wiki-attachments-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method_and_count(client, 'get', url, None, users)
assert results == [(200, 2), (200, 2), (200, 3), (200, 3), (200, 3)]

View File

@ -0,0 +1,52 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
def test_auth_create(client):
url = reverse('auth-list')
user = f.UserFactory.create()
login_data = json.dumps({
"type": "normal",
"username": user.username,
"password": user.username,
})
result = client.post(url, login_data, content_type="application/json")
assert result.status_code == 200
def test_auth_action_register(client, settings):
settings.PUBLIC_REGISTER_ENABLED = True
url = reverse('auth-register')
register_data = json.dumps({
"type": "public",
"username": "test",
"password": "test",
"full_name": "test",
"email": "test@test.com",
})
result = client.post(url, register_data, content_type="application/json")
assert result.status_code == 201

View File

@ -0,0 +1,167 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
m.public_user_story = f.UserStoryFactory(project=m.public_project, ref=1)
m.public_task = f.TaskFactory(project=m.public_project, ref=2)
m.public_issue = f.IssueFactory(project=m.public_project, ref=3)
m.public_wiki = f.WikiPageFactory(project=m.public_project, slug=4)
m.private_user_story1 = f.UserStoryFactory(project=m.private_project1, ref=5)
m.private_task1 = f.TaskFactory(project=m.private_project1, ref=6)
m.private_issue1 = f.IssueFactory(project=m.private_project1, ref=7)
m.private_wiki1 = f.WikiPageFactory(project=m.private_project1, slug=8)
m.private_user_story2 = f.UserStoryFactory(project=m.private_project2, ref=9)
m.private_task2 = f.TaskFactory(project=m.private_project2, ref=10)
m.private_issue2 = f.IssueFactory(project=m.private_project2, ref=11)
m.private_wiki2 = f.WikiPageFactory(project=m.private_project2, slug=12)
return m
def test_user_story_history_retrieve(client, data):
public_url = reverse('userstory-history-detail', kwargs={"pk": data.public_user_story.pk})
private_url1 = reverse('userstory-history-detail', kwargs={"pk": data.private_user_story1.pk})
private_url2 = reverse('userstory-history-detail', kwargs={"pk": data.private_user_story2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_task_history_retrieve(client, data):
public_url = reverse('task-history-detail', kwargs={"pk": data.public_task.pk})
private_url1 = reverse('task-history-detail', kwargs={"pk": data.private_task1.pk})
private_url2 = reverse('task-history-detail', kwargs={"pk": data.private_task2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_issue_history_retrieve(client, data):
public_url = reverse('issue-history-detail', kwargs={"pk": data.public_issue.pk})
private_url1 = reverse('issue-history-detail', kwargs={"pk": data.private_issue1.pk})
private_url2 = reverse('issue-history-detail', kwargs={"pk": data.private_issue2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_wiki_history_retrieve(client, data):
public_url = reverse('wiki-history-detail', kwargs={"pk": data.public_wiki.pk})
private_url1 = reverse('wiki-history-detail', kwargs={"pk": data.private_wiki1.pk})
private_url2 = reverse('wiki-history-detail', kwargs={"pk": data.private_wiki2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -0,0 +1,359 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.projects.issues.serializers import IssueSerializer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
from taiga.projects.votes.services import add_vote
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
m.public_issue = f.IssueFactory(project=m.public_project,
status__project=m.public_project,
severity__project=m.public_project,
priority__project=m.public_project,
type__project=m.public_project,
milestone__project=m.public_project)
m.private_issue1 = f.IssueFactory(project=m.private_project1,
status__project=m.private_project1,
severity__project=m.private_project1,
priority__project=m.private_project1,
type__project=m.private_project1,
milestone__project=m.private_project1)
m.private_issue2 = f.IssueFactory(project=m.private_project2,
status__project=m.private_project2,
severity__project=m.private_project2,
priority__project=m.private_project2,
type__project=m.private_project2,
milestone__project=m.private_project2)
return m
def test_issue_retrieve(client, data):
public_url = reverse('issues-detail', kwargs={"pk": data.public_issue.pk})
private_url1 = reverse('issues-detail', kwargs={"pk": data.private_issue1.pk})
private_url2 = reverse('issues-detail', kwargs={"pk": data.private_issue2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_issue_update(client, data):
public_url = reverse('issues-detail', kwargs={"pk": data.public_issue.pk})
private_url1 = reverse('issues-detail', kwargs={"pk": data.private_issue1.pk})
private_url2 = reverse('issues-detail', kwargs={"pk": data.private_issue2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
issue_data = IssueSerializer(data.public_issue).data
issue_data["subject"] = "test"
issue_data = JSONRenderer().render(issue_data)
results = helper_test_http_method(client, 'put', public_url, issue_data, users)
assert results == [401, 403, 403, 200, 200]
issue_data = IssueSerializer(data.private_issue1).data
issue_data["subject"] = "test"
issue_data = JSONRenderer().render(issue_data)
results = helper_test_http_method(client, 'put', private_url1, issue_data, users)
assert results == [401, 403, 403, 200, 200]
issue_data = IssueSerializer(data.private_issue2).data
issue_data["subject"] = "test"
issue_data = JSONRenderer().render(issue_data)
results = helper_test_http_method(client, 'put', private_url2, issue_data, users)
assert results == [401, 403, 403, 200, 200]
def test_issue_delete(client, data):
public_url = reverse('issues-detail', kwargs={"pk": data.public_issue.pk})
private_url1 = reverse('issues-detail', kwargs={"pk": data.private_issue1.pk})
private_url2 = reverse('issues-detail', kwargs={"pk": data.private_issue2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_issue_list(client, data):
url = reverse('issues-list')
response = client.get(url)
issues_data = json.loads(response.content.decode('utf-8'))
assert len(issues_data) == 2
assert response.status_code == 200
client.login(data.registered_user)
response = client.get(url)
issues_data = json.loads(response.content.decode('utf-8'))
assert len(issues_data) == 2
assert response.status_code == 200
client.login(data.project_member_with_perms)
response = client.get(url)
issues_data = json.loads(response.content.decode('utf-8'))
assert len(issues_data) == 3
assert response.status_code == 200
client.login(data.project_owner)
response = client.get(url)
issues_data = json.loads(response.content.decode('utf-8'))
assert len(issues_data) == 3
assert response.status_code == 200
def test_issue_create(client, data):
url = reverse('issues-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
create_data = json.dumps({
"subject": "test",
"ref": 1,
"project": data.public_project.pk,
"severity": data.public_project.severities.all()[0].pk,
"priority": data.public_project.priorities.all()[0].pk,
"status": data.public_project.issue_statuses.all()[0].pk,
"type": data.public_project.issue_types.all()[0].pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users)
assert results == [401, 201, 201, 201, 201]
create_data = json.dumps({
"subject": "test",
"ref": 2,
"project": data.private_project1.pk,
"severity": data.private_project1.severities.all()[0].pk,
"priority": data.private_project1.priorities.all()[0].pk,
"status": data.private_project1.issue_statuses.all()[0].pk,
"type": data.private_project1.issue_types.all()[0].pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users)
assert results == [401, 201, 201, 201, 201]
create_data = json.dumps({
"subject": "test",
"ref": 3,
"project": data.private_project2.pk,
"severity": data.private_project2.severities.all()[0].pk,
"priority": data.private_project2.priorities.all()[0].pk,
"status": data.private_project2.issue_statuses.all()[0].pk,
"type": data.private_project2.issue_types.all()[0].pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users)
assert results == [401, 403, 403, 201, 201]
def test_issue_patch(client, data):
public_url = reverse('issues-detail', kwargs={"pk": data.public_issue.pk})
private_url1 = reverse('issues-detail', kwargs={"pk": data.private_issue1.pk})
private_url2 = reverse('issues-detail', kwargs={"pk": data.private_issue2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
patch_data = json.dumps({"subject": "test", "version": data.public_issue.version})
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
assert results == [401, 403, 403, 200, 200]
patch_data = json.dumps({"subject": "test", "version": data.private_issue1.version})
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
assert results == [401, 403, 403, 200, 200]
patch_data = json.dumps({"subject": "test", "version": data.private_issue2.version})
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
assert results == [401, 403, 403, 200, 200]
def test_issue_action_upvote(client, data):
public_url = reverse('issues-upvote', kwargs={"pk": data.public_issue.pk})
private_url1 = reverse('issues-upvote', kwargs={"pk": data.private_issue1.pk})
private_url2 = reverse('issues-upvote', kwargs={"pk": data.private_issue2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [401, 403, 403, 200, 200]
def test_issue_action_downvote(client, data):
public_url = reverse('issues-downvote', kwargs={"pk": data.public_issue.pk})
private_url1 = reverse('issues-downvote', kwargs={"pk": data.private_issue1.pk})
private_url2 = reverse('issues-downvote', kwargs={"pk": data.private_issue2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [401, 403, 403, 200, 200]
def test_issue_voters_list(client, data):
public_url = reverse('issue-voters-list', kwargs={"issue_id": data.public_issue.pk})
private_url1 = reverse('issue-voters-list', kwargs={"issue_id": data.private_issue1.pk})
private_url2 = reverse('issue-voters-list', kwargs={"issue_id": data.private_issue2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_issue_voters_retrieve(client, data):
add_vote(data.public_issue, data.project_owner)
public_url = reverse('issue-voters-detail', kwargs={"issue_id": data.public_issue.pk, "pk": data.project_owner.pk})
add_vote(data.private_issue1, data.project_owner)
private_url1 = reverse('issue-voters-detail', kwargs={"issue_id": data.private_issue1.pk, "pk": data.project_owner.pk})
add_vote(data.private_issue2, data.project_owner)
private_url2 = reverse('issue-voters-detail', kwargs={"issue_id": data.private_issue2.pk, "pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -0,0 +1,263 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.projects.milestones.serializers import MilestoneSerializer
from taiga.projects.milestones.models import Milestone
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
m.public_milestone = f.MilestoneFactory(project=m.public_project)
m.private_milestone1 = f.MilestoneFactory(project=m.private_project1)
m.private_milestone2 = f.MilestoneFactory(project=m.private_project2)
return m
def test_milestone_retrieve(client, data):
public_url = reverse('milestones-detail', kwargs={"pk": data.public_milestone.pk})
private_url1 = reverse('milestones-detail', kwargs={"pk": data.private_milestone1.pk})
private_url2 = reverse('milestones-detail', kwargs={"pk": data.private_milestone2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_milestone_update(client, data):
public_url = reverse('milestones-detail', kwargs={"pk": data.public_milestone.pk})
private_url1 = reverse('milestones-detail', kwargs={"pk": data.private_milestone1.pk})
private_url2 = reverse('milestones-detail', kwargs={"pk": data.private_milestone2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
milestone_data = MilestoneSerializer(data.public_milestone).data
milestone_data["name"] = "test"
milestone_data = JSONRenderer().render(milestone_data)
results = helper_test_http_method(client, 'put', public_url, milestone_data, users)
assert results == [401, 403, 403, 200, 200]
milestone_data = MilestoneSerializer(data.private_milestone1).data
milestone_data["name"] = "test"
milestone_data = JSONRenderer().render(milestone_data)
results = helper_test_http_method(client, 'put', private_url1, milestone_data, users)
assert results == [401, 403, 403, 200, 200]
milestone_data = MilestoneSerializer(data.private_milestone2).data
milestone_data["name"] = "test"
milestone_data = JSONRenderer().render(milestone_data)
results = helper_test_http_method(client, 'put', private_url2, milestone_data, users)
assert results == [401, 403, 403, 200, 200]
def test_milestone_delete(client, data):
public_url = reverse('milestones-detail', kwargs={"pk": data.public_milestone.pk})
private_url1 = reverse('milestones-detail', kwargs={"pk": data.private_milestone1.pk})
private_url2 = reverse('milestones-detail', kwargs={"pk": data.private_milestone2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_milestone_list(client, data):
url = reverse('milestones-list')
response = client.get(url)
milestones_data = json.loads(response.content.decode('utf-8'))
assert len(milestones_data) == 2
assert response.status_code == 200
client.login(data.registered_user)
response = client.get(url)
milestones_data = json.loads(response.content.decode('utf-8'))
assert len(milestones_data) == 2
assert response.status_code == 200
client.login(data.project_member_with_perms)
response = client.get(url)
milestones_data = json.loads(response.content.decode('utf-8'))
assert len(milestones_data) == 3
assert response.status_code == 200
client.login(data.project_owner)
response = client.get(url)
milestones_data = json.loads(response.content.decode('utf-8'))
assert len(milestones_data) == 3
assert response.status_code == 200
def test_milestone_create(client, data):
url = reverse('milestones-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
create_data = json.dumps({
"name": "test",
"estimated_start": "2014-12-10",
"estimated_finish": "2014-12-24",
"project": data.public_project.pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: Milestone.objects.all().delete())
assert results == [401, 403, 403, 201, 201]
create_data = json.dumps({
"name": "test",
"estimated_start": "2014-12-10",
"estimated_finish": "2014-12-24",
"project": data.private_project1.pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: Milestone.objects.all().delete())
assert results == [401, 403, 403, 201, 201]
create_data = json.dumps({
"name": "test",
"estimated_start": "2014-12-10",
"estimated_finish": "2014-12-24",
"project": data.private_project2.pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: Milestone.objects.all().delete())
assert results == [401, 403, 403, 201, 201]
def test_milestone_patch(client, data):
public_url = reverse('milestones-detail', kwargs={"pk": data.public_milestone.pk})
private_url1 = reverse('milestones-detail', kwargs={"pk": data.private_milestone1.pk})
private_url2 = reverse('milestones-detail', kwargs={"pk": data.private_milestone2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
patch_data = json.dumps({"name": "test"})
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
assert results == [401, 403, 403, 200, 200]
patch_data = json.dumps({"name": "test"})
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
assert results == [401, 403, 403, 200, 200]
patch_data = json.dumps({"name": "test"})
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
assert results == [401, 403, 403, 200, 200]
def test_milestone_action_stats(client, data):
public_url = reverse('milestones-stats', kwargs={"pk": data.public_milestone.pk})
private_url1 = reverse('milestones-stats', kwargs={"pk": data.private_milestone1.pk})
private_url2 = reverse('milestones-stats', kwargs={"pk": data.private_milestone2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,381 @@
import pytest
from django.core.urlresolvers import reverse
from django.db.models.loading import get_model
from rest_framework.renderers import JSONRenderer
from taiga.projects.serializers import ProjectDetailSerializer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, helper_test_http_method_and_count
import json
pytestmark = pytest.mark.django_db
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.superuser = f.UserFactory.create(is_superuser=True)
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=['view_project'],
public_permissions=['view_project'])
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=['view_project'],
public_permissions=['view_project'],
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
f.RoleFactory(project=m.public_project)
m.membership = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.membership = f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.membership = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.membership = f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
ContentType = get_model("contenttypes", "ContentType")
Project = get_model("projects", "Project")
project_ct = ContentType.objects.get_for_model(Project)
f.VoteFactory(content_type=project_ct, object_id=m.public_project.pk, user=m.project_member_with_perms)
f.VoteFactory(content_type=project_ct, object_id=m.public_project.pk, user=m.project_owner)
f.VoteFactory(content_type=project_ct, object_id=m.private_project1.pk, user=m.project_member_with_perms)
f.VoteFactory(content_type=project_ct, object_id=m.private_project1.pk, user=m.project_owner)
f.VoteFactory(content_type=project_ct, object_id=m.private_project2.pk, user=m.project_member_with_perms)
f.VoteFactory(content_type=project_ct, object_id=m.private_project2.pk, user=m.project_owner)
f.VotesFactory(content_type=project_ct, object_id=m.public_project.pk, count=2)
f.VotesFactory(content_type=project_ct, object_id=m.private_project1.pk, count=2)
f.VotesFactory(content_type=project_ct, object_id=m.private_project2.pk, count=2)
return m
def test_project_retrieve(client, data):
public_url = reverse('projects-detail', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-detail', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-detail', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [401, 403, 200, 200]
def test_project_update(client, data):
url = reverse('projects-detail', kwargs={"pk": data.private_project2.pk})
project_data = ProjectDetailSerializer(data.private_project2).data
project_data["is_private"] = False
project_data = JSONRenderer().render(project_data)
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'put', url, project_data, users)
assert results == [401, 403, 403, 200]
def test_project_delete(client, data):
url = reverse('projects-detail', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'delete', url, None, users)
assert results == [401, 403, 403, 204]
def test_project_list(client, data):
url = reverse('projects-list')
response = client.get(url)
projects_data = json.loads(response.content.decode('utf-8'))
assert len(projects_data) == 2
assert response.status_code == 200
client.login(data.registered_user)
response = client.get(url)
projects_data = json.loads(response.content.decode('utf-8'))
assert len(projects_data) == 2
assert response.status_code == 200
client.login(data.project_member_with_perms)
response = client.get(url)
projects_data = json.loads(response.content.decode('utf-8'))
assert len(projects_data) == 3
assert response.status_code == 200
client.login(data.project_owner)
response = client.get(url)
projects_data = json.loads(response.content.decode('utf-8'))
assert len(projects_data) == 3
assert response.status_code == 200
def test_project_patch(client, data):
url = reverse('projects-detail', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
data = json.dumps({"is_private": False})
results = helper_test_http_method(client, 'patch', url, data, users)
assert results == [401, 403, 403, 200]
def test_project_action_stats(client, data):
public_url = reverse('projects-stats', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-stats', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-stats', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [404, 404, 200, 200]
def test_project_action_star(client, data):
public_url = reverse('projects-star', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-star', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-star', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, None, users)
assert results == [401, 200, 200, 200]
results = helper_test_http_method(client, 'post', private1_url, None, users)
assert results == [401, 200, 200, 200]
results = helper_test_http_method(client, 'post', private2_url, None, users)
assert results == [404, 404, 200, 200]
def test_project_action_unstar(client, data):
public_url = reverse('projects-unstar', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-unstar', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-unstar', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, None, users)
assert results == [401, 200, 200, 200]
results = helper_test_http_method(client, 'post', private1_url, None, users)
assert results == [401, 200, 200, 200]
results = helper_test_http_method(client, 'post', private2_url, None, users)
assert results == [404, 404, 200, 200]
def test_project_action_issues_stats(client, data):
public_url = reverse('projects-issues-stats', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-issues-stats', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-issues-stats', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [404, 404, 200, 200]
def test_project_action_issues_filters_data(client, data):
public_url = reverse('projects-issue-filters-data', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-issue-filters-data', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-issue-filters-data', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [404, 404, 200, 200]
def test_project_action_tags(client, data):
public_url = reverse('projects-tags', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-tags', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-tags', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [404, 404, 200, 200]
def test_project_action_fans(client, data):
public_url = reverse('projects-fans', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-fans', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-fans', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method_and_count(client, 'get', public_url, None, users)
assert results == [(200, 2), (200, 2), (200, 2), (200, 2), (200, 2)]
results = helper_test_http_method_and_count(client, 'get', private1_url, None, users)
assert results == [(200, 2), (200, 2), (200, 2), (200, 2), (200, 2)]
results = helper_test_http_method_and_count(client, 'get', private2_url, None, users)
assert results == [(404, 1), (404, 1), (403, 2), (200, 2), (200, 2)]
def test_user_action_starred(client, data):
url1 = reverse('users-starred', kwargs={"pk": data.project_member_without_perms.pk})
url2 = reverse('users-starred', kwargs={"pk": data.project_member_with_perms.pk})
url3 = reverse('users-starred', kwargs={"pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method_and_count(client, 'get', url1, None, users)
assert results == [(200, 0), (200, 0), (200, 0), (200, 0), (200, 0)]
results = helper_test_http_method_and_count(client, 'get', url2, None, users)
assert results == [(200, 3), (200, 3), (200, 3), (200, 3), (200, 3)]
results = helper_test_http_method_and_count(client, 'get', url3, None, users)
assert results == [(200, 3), (200, 3), (200, 3), (200, 3), (200, 3)]
def test_project_action_create_template(client, data):
public_url = reverse('projects-create-template', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-create-template', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-create-template', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner,
data.superuser,
]
template_data = json.dumps({
"template_name": "test",
"template_description": "test",
})
results = helper_test_http_method(client, 'post', public_url, template_data, users)
assert results == [401, 403, 403, 403, 403, 201]
results = helper_test_http_method(client, 'post', private1_url, template_data, users)
assert results == [401, 403, 403, 403, 403, 201]
results = helper_test_http_method(client, 'post', private2_url, template_data, users)
assert results == [404, 404, 403, 403, 403, 201]
def test_invitations_list(client, data):
url = reverse('invitations-list')
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', url, None, users)
assert results == [403, 403, 403, 403]
def test_invitations_retrieve(client, data):
invitation = f.MembershipFactory(user=None)
url = reverse('invitations-detail', kwargs={'token': invitation.token})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', url, None, users)
assert results == [200, 200, 200, 200]

View File

@ -0,0 +1,108 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner,
slug="public")
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner,
slug="private1")
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner,
slug="private2")
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
m.view_only_membership = f.MembershipFactory(project=m.private_project2,
user=m.other_user,
role__project=m.private_project2,
role__permissions=["view_project"])
f.UserStoryFactory(project=m.private_project2, ref=1, pk=1)
f.TaskFactory(project=m.private_project2, ref=2, pk=1)
f.IssueFactory(project=m.private_project2, ref=3, pk=1)
m.milestone = f.MilestoneFactory(project=m.private_project2, slug=4, pk=1)
return m
def test_resolver_list(client, data):
url = reverse('resolver-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', "{}?project=public".format(url), None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', "{}?project=private1".format(url), None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', "{}?project=private2".format(url), None, users)
assert results == [401, 403, 403, 200, 200]
client.login(data.other_user)
response = client.get("{}?project=private2&us=1&task=2&issue=3&milestone=4".format(url))
assert json.loads(response.content.decode('utf-8')) == {"project": data.private_project2.pk}
client.login(data.project_owner)
response = client.get("{}?project=private2&us=1&task=2&issue=3&milestone=4".format(url))
assert json.loads(response.content.decode('utf-8')) == {"project": data.private_project2.pk, "us": 1, "task": 1, "issue": 1, "milestone": data.milestone.pk}

View File

@ -0,0 +1,109 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.projects.issues.serializers import IssueSerializer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method_and_keys, disconnect_signals, reconnect_signals
from taiga.projects.votes.services import add_vote
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
m.public_issue = f.IssueFactory(project=m.public_project,
status__project=m.public_project,
severity__project=m.public_project,
priority__project=m.public_project,
type__project=m.public_project,
milestone__project=m.public_project)
m.private_issue1 = f.IssueFactory(project=m.private_project1,
status__project=m.private_project1,
severity__project=m.private_project1,
priority__project=m.private_project1,
type__project=m.private_project1,
milestone__project=m.private_project1)
m.private_issue2 = f.IssueFactory(project=m.private_project2,
status__project=m.private_project2,
severity__project=m.private_project2,
priority__project=m.private_project2,
type__project=m.private_project2,
milestone__project=m.private_project2)
return m
def test_search_list(client, data):
url = reverse('search-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method_and_keys(client, 'get', url, {'project': data.public_project.pk}, users)
all_keys = set(['count', 'userstories', 'issues', 'tasks', 'wikipages'])
assert results == [(200, all_keys), (200, all_keys), (200, all_keys), (200, all_keys), (200, all_keys)]
results = helper_test_http_method_and_keys(client, 'get', url, {'project': data.private_project1.pk}, users)
assert results == [(200, all_keys), (200, all_keys), (200, all_keys), (200, all_keys), (200, all_keys)]
results = helper_test_http_method_and_keys(client, 'get', url, {'project': data.private_project2.pk}, users)
assert results == [(200, set(['count'])), (200, set(['count'])), (200, set(['count'])), (200, all_keys), (200, all_keys)]

View File

@ -0,0 +1,125 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.userstorage.serializers import StorageEntrySerializer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, helper_test_http_method_and_count, disconnect_signals, reconnect_signals
from taiga.projects.votes.services import add_vote
from taiga.userstorage.models import StorageEntry
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.user1 = f.UserFactory.create()
m.user2 = f.UserFactory.create()
m.storage_user1 = f.StorageEntryFactory(owner=m.user1)
m.storage_user2 = f.StorageEntryFactory(owner=m.user2)
m.storage2_user2 = f.StorageEntryFactory(owner=m.user2)
return m
def test_storage_retrieve(client, data):
url = reverse('user-storage-detail', kwargs={"key": data.storage_user1.key})
users = [
None,
data.user1,
data.user2,
]
results = helper_test_http_method(client, 'get', url, None, users)
assert results == [401, 200, 404]
def test_storage_update(client, data):
url = reverse('user-storage-detail', kwargs={"key": data.storage_user1.key})
users = [
None,
data.user1,
data.user2,
]
storage_data = StorageEntrySerializer(data.storage_user1).data
storage_data["key"] = "test"
storage_data = JSONRenderer().render(storage_data)
results = helper_test_http_method(client, 'put', url, storage_data, users)
assert results == [401, 200, 201]
def test_storage_delete(client, data):
url = reverse('user-storage-detail', kwargs={"key": data.storage_user1.key})
users = [
None,
data.user1,
data.user2,
]
results = helper_test_http_method(client, 'delete', url, None, users)
assert results == [401, 204, 404]
def test_storage_list(client, data):
url = reverse('user-storage-list')
users = [
None,
data.user1,
data.user2,
]
results = helper_test_http_method_and_count(client, 'get', url, None, users)
assert results == [(200, 0), (200, 1), (200, 2)]
def test_storage_create(client, data):
url = reverse('user-storage-list')
users = [
None,
data.user1,
data.user2,
]
create_data = json.dumps({
"key": "test",
"value": "test",
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: StorageEntry.objects.all().delete())
assert results == [401, 201, 201]
def test_storage_patch(client, data):
url = reverse('user-storage-detail', kwargs={"key": data.storage_user1.key})
users = [
None,
data.user1,
data.user2,
]
patch_data = json.dumps({"value": "test"})
results = helper_test_http_method(client, 'patch', url, patch_data, users)
assert results == [401, 200, 201]

View File

@ -0,0 +1,291 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.projects.tasks.serializers import TaskSerializer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
m.public_task = f.TaskFactory(project=m.public_project,
status__project=m.public_project,
milestone__project=m.public_project,
user_story__project=m.public_project)
m.private_task1 = f.TaskFactory(project=m.private_project1,
status__project=m.private_project1,
milestone__project=m.private_project1,
user_story__project=m.private_project1)
m.private_task2 = f.TaskFactory(project=m.private_project2,
status__project=m.private_project2,
milestone__project=m.private_project2,
user_story__project=m.private_project2)
m.public_project.default_task_status = m.public_task.status
m.public_project.save()
m.private_project1.default_task_status = m.private_task1.status
m.private_project1.save()
m.private_project2.default_task_status = m.private_task2.status
m.private_project2.save()
return m
def test_task_retrieve(client, data):
public_url = reverse('tasks-detail', kwargs={"pk": data.public_task.pk})
private_url1 = reverse('tasks-detail', kwargs={"pk": data.private_task1.pk})
private_url2 = reverse('tasks-detail', kwargs={"pk": data.private_task2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_task_update(client, data):
public_url = reverse('tasks-detail', kwargs={"pk": data.public_task.pk})
private_url1 = reverse('tasks-detail', kwargs={"pk": data.private_task1.pk})
private_url2 = reverse('tasks-detail', kwargs={"pk": data.private_task2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
task_data = TaskSerializer(data.public_task).data
task_data["subject"] = "test"
task_data = JSONRenderer().render(task_data)
results = helper_test_http_method(client, 'put', public_url, task_data, users)
assert results == [401, 403, 403, 200, 200]
task_data = TaskSerializer(data.private_task1).data
task_data["subject"] = "test"
task_data = JSONRenderer().render(task_data)
results = helper_test_http_method(client, 'put', private_url1, task_data, users)
assert results == [401, 403, 403, 200, 200]
task_data = TaskSerializer(data.private_task2).data
task_data["subject"] = "test"
task_data = JSONRenderer().render(task_data)
results = helper_test_http_method(client, 'put', private_url2, task_data, users)
assert results == [401, 403, 403, 200, 200]
def test_task_delete(client, data):
public_url = reverse('tasks-detail', kwargs={"pk": data.public_task.pk})
private_url1 = reverse('tasks-detail', kwargs={"pk": data.private_task1.pk})
private_url2 = reverse('tasks-detail', kwargs={"pk": data.private_task2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_task_list(client, data):
url = reverse('tasks-list')
response = client.get(url)
tasks_data = json.loads(response.content.decode('utf-8'))
assert len(tasks_data) == 2
assert response.status_code == 200
client.login(data.registered_user)
response = client.get(url)
tasks_data = json.loads(response.content.decode('utf-8'))
assert len(tasks_data) == 2
assert response.status_code == 200
client.login(data.project_member_with_perms)
response = client.get(url)
tasks_data = json.loads(response.content.decode('utf-8'))
assert len(tasks_data) == 3
assert response.status_code == 200
client.login(data.project_owner)
response = client.get(url)
tasks_data = json.loads(response.content.decode('utf-8'))
assert len(tasks_data) == 3
assert response.status_code == 200
def test_task_create(client, data):
url = reverse('tasks-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
create_data = json.dumps({
"subject": "test",
"ref": 1,
"project": data.public_project.pk,
"status": data.public_project.task_statuses.all()[0].pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users)
assert results == [401, 403, 403, 201, 201]
create_data = json.dumps({
"subject": "test",
"ref": 2,
"project": data.private_project1.pk,
"status": data.private_project1.task_statuses.all()[0].pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users)
assert results == [401, 403, 403, 201, 201]
create_data = json.dumps({
"subject": "test",
"ref": 3,
"project": data.private_project2.pk,
"status": data.private_project2.task_statuses.all()[0].pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users)
assert results == [401, 403, 403, 201, 201]
def test_task_patch(client, data):
public_url = reverse('tasks-detail', kwargs={"pk": data.public_task.pk})
private_url1 = reverse('tasks-detail', kwargs={"pk": data.private_task1.pk})
private_url2 = reverse('tasks-detail', kwargs={"pk": data.private_task2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
patch_data = json.dumps({"subject": "test", "version": data.public_task.version})
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
assert results == [401, 403, 403, 200, 200]
patch_data = json.dumps({"subject": "test", "version": data.private_task1.version})
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
assert results == [401, 403, 403, 200, 200]
patch_data = json.dumps({"subject": "test", "version": data.private_task2.version})
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
assert results == [401, 403, 403, 200, 200]
def test_task_action_bulk_create(client, data):
url = reverse('tasks-bulk-create')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
bulk_data = json.dumps({
"bulkTasks": "test1\ntest2",
"usId": data.public_task.user_story.pk,
"projectId": data.public_task.project.pk,
})
results = helper_test_http_method(client, 'post', url, bulk_data, users)
assert results == [401, 403, 403, 200, 200]
bulk_data = json.dumps({
"bulkTasks": "test1\ntest2",
"usId": data.private_task1.user_story.pk,
"projectId": data.private_task1.project.pk,
})
results = helper_test_http_method(client, 'post', url, bulk_data, users)
assert results == [401, 403, 403, 200, 200]
bulk_data = json.dumps({
"bulkTasks": "test1\ntest2",
"usId": data.private_task2.user_story.pk,
"projectId": data.private_task2.project.pk,
})
results = helper_test_http_method(client, 'post', url, bulk_data, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -0,0 +1,104 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
return m
def test_user_timeline_retrieve(client, data):
url = reverse('user-timeline-detail', kwargs={"pk": data.registered_user.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', url, None, users)
assert results == [200, 200, 200, 200, 200]
def test_project_timeline_retrieve(client, data):
public_url = reverse('project-timeline-detail', kwargs={"pk": data.public_project.pk})
private_url1 = reverse('project-timeline-detail', kwargs={"pk": data.private_project1.pk})
private_url2 = reverse('project-timeline-detail', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -0,0 +1,230 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.users.serializers import UserSerializer
from taiga.users.models import User
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.superuser = f.UserFactory.create(is_superuser=True)
return m
def test_user_retrieve(client, data):
url = reverse('users-detail', kwargs={"pk": data.registered_user.pk})
users = [
None,
data.registered_user,
data.other_user,
data.superuser,
]
results = helper_test_http_method(client, 'get', url, None, users)
assert results == [200, 200, 200, 200]
def test_user_update(client, data):
url = reverse('users-detail', kwargs={"pk": data.registered_user.pk})
users = [
None,
data.registered_user,
data.other_user,
data.superuser,
]
user_data = UserSerializer(data.registered_user).data
user_data["full_name"] = "test"
user_data = JSONRenderer().render(user_data)
results = helper_test_http_method(client, 'put', url, user_data, users)
assert results == [401, 200, 403, 200]
def test_user_delete(client, data):
url = reverse('users-detail', kwargs={"pk": data.registered_user.pk})
users = [
None,
data.other_user,
data.registered_user,
]
results = helper_test_http_method(client, 'delete', url, None, users)
assert results == [401, 403, 204]
def test_user_list(client, data):
url = reverse('users-list')
response = client.get(url)
users_data = json.loads(response.content.decode('utf-8'))
assert len(users_data) == 3
assert response.status_code == 200
client.login(data.registered_user)
response = client.get(url)
users_data = json.loads(response.content.decode('utf-8'))
assert len(users_data) == 3
assert response.status_code == 200
client.login(data.other_user)
response = client.get(url)
users_data = json.loads(response.content.decode('utf-8'))
assert len(users_data) == 3
assert response.status_code == 200
client.login(data.superuser)
response = client.get(url)
users_data = json.loads(response.content.decode('utf-8'))
assert len(users_data) == 3
assert response.status_code == 200
def test_user_create(client, data):
url = reverse('users-list')
users = [
None,
data.registered_user,
data.other_user,
data.superuser,
]
create_data = json.dumps({
"username": "test",
"full_name": "test",
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: User.objects.filter(username="test").delete())
assert results == [201, 201, 201, 201]
def test_user_patch(client, data):
url = reverse('users-detail', kwargs={"pk": data.registered_user.pk})
users = [
None,
data.registered_user,
data.other_user,
data.superuser,
]
patch_data = json.dumps({"full_name": "test"})
results = helper_test_http_method(client, 'patch', url, patch_data, users)
assert results == [401, 200, 403, 200]
def test_user_action_change_password(client, data):
url = reverse('users-change-password')
users = [
None,
data.registered_user,
data.other_user,
data.superuser,
]
patch_data = json.dumps({"password": "test-password"})
results = helper_test_http_method(client, 'post', url, patch_data, users)
assert results == [401, 204, 204, 204]
def test_user_action_change_password_from_recovery(client, data):
url = reverse('users-change-password-from-recovery')
new_user = f.UserFactory(token="test-token")
def reset_token():
new_user.token = "test-token"
new_user.save()
users = [
None,
data.registered_user,
data.other_user,
data.superuser,
]
patch_data = json.dumps({"password": "test-password", "token": "test-token"})
results = helper_test_http_method(client, 'post', url, patch_data, users, reset_token)
assert results == [204, 204, 204, 204]
def test_user_action_password_recovery(client, data):
url = reverse('users-password-recovery')
new_user = f.UserFactory.create(username="test")
users = [
None,
data.registered_user,
data.other_user,
data.superuser,
]
patch_data = json.dumps({"username": "test"})
results = helper_test_http_method(client, 'post', url, patch_data, users)
assert results == [200, 200, 200, 200]
# def test_membership_retrieve(client, data):
# assert False
#
#
# def test_membership_update(client, data):
# assert False
#
#
# def test_membership_delete(client, data):
# assert False
#
#
# def test_membership_list(client, data):
# assert False
#
#
# def test_membership_patch(client, data):
# assert False
#
#
# def test_invitation_retrieve(client, data):
# assert False
#
#
# def test_invitation_update(client, data):
# assert False
#
#
# def test_invitation_delete(client, data):
# assert False
#
#
# def test_invitation_list(client, data):
# assert False
#
#
# def test_invitation_patch(client, data):
# assert False

View File

@ -0,0 +1,296 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.projects.userstories.serializers import UserStorySerializer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
m.public_points = f.PointsFactory(project=m.public_project)
m.private_points1 = f.PointsFactory(project=m.private_project1)
m.private_points2 = f.PointsFactory(project=m.private_project2)
m.public_role_points = f.RolePointsFactory(role=m.public_project.roles.all()[0],
points=m.public_points,
user_story__project=m.public_project)
m.private_role_points1 = f.RolePointsFactory(role=m.private_project1.roles.all()[0],
points=m.private_points1,
user_story__project=m.private_project1)
m.private_role_points2 = f.RolePointsFactory(role=m.private_project2.roles.all()[0],
points=m.private_points2,
user_story__project=m.private_project2)
m.public_user_story = m.public_role_points.user_story
m.private_user_story1 = m.private_role_points1.user_story
m.private_user_story2 = m.private_role_points2.user_story
return m
def test_user_story_retrieve(client, data):
public_url = reverse('userstories-detail', kwargs={"pk": data.public_user_story.pk})
private_url1 = reverse('userstories-detail', kwargs={"pk": data.private_user_story1.pk})
private_url2 = reverse('userstories-detail', kwargs={"pk": data.private_user_story2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_user_story_update(client, data):
public_url = reverse('userstories-detail', kwargs={"pk": data.public_user_story.pk})
private_url1 = reverse('userstories-detail', kwargs={"pk": data.private_user_story1.pk})
private_url2 = reverse('userstories-detail', kwargs={"pk": data.private_user_story2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
user_story_data = UserStorySerializer(data.public_user_story).data
user_story_data["subject"] = "test"
user_story_data = JSONRenderer().render(user_story_data)
results = helper_test_http_method(client, 'put', public_url, user_story_data, users)
assert results == [401, 403, 403, 200, 200]
user_story_data = UserStorySerializer(data.private_user_story1).data
user_story_data["subject"] = "test"
user_story_data = JSONRenderer().render(user_story_data)
results = helper_test_http_method(client, 'put', private_url1, user_story_data, users)
assert results == [401, 403, 403, 200, 200]
user_story_data = UserStorySerializer(data.private_user_story2).data
user_story_data["subject"] = "test"
user_story_data = JSONRenderer().render(user_story_data)
results = helper_test_http_method(client, 'put', private_url2, user_story_data, users)
assert results == [401, 403, 403, 200, 200]
def test_user_story_delete(client, data):
public_url = reverse('userstories-detail', kwargs={"pk": data.public_user_story.pk})
private_url1 = reverse('userstories-detail', kwargs={"pk": data.private_user_story1.pk})
private_url2 = reverse('userstories-detail', kwargs={"pk": data.private_user_story2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_user_story_list(client, data):
url = reverse('userstories-list')
response = client.get(url)
userstories_data = json.loads(response.content.decode('utf-8'))
assert len(userstories_data) == 2
assert response.status_code == 200
client.login(data.registered_user)
response = client.get(url)
userstories_data = json.loads(response.content.decode('utf-8'))
assert len(userstories_data) == 2
assert response.status_code == 200
client.login(data.project_member_with_perms)
response = client.get(url)
userstories_data = json.loads(response.content.decode('utf-8'))
assert len(userstories_data) == 3
assert response.status_code == 200
client.login(data.project_owner)
response = client.get(url)
userstories_data = json.loads(response.content.decode('utf-8'))
assert len(userstories_data) == 3
assert response.status_code == 200
def test_user_story_create(client, data):
url = reverse('userstories-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
create_data = json.dumps({"subject": "test", "ref": 1, "project": data.public_project.pk})
results = helper_test_http_method(client, 'post', url, create_data, users)
assert results == [401, 201, 201, 201, 201]
create_data = json.dumps({"subject": "test", "ref": 2, "project": data.private_project1.pk})
results = helper_test_http_method(client, 'post', url, create_data, users)
assert results == [401, 201, 201, 201, 201]
create_data = json.dumps({"subject": "test", "ref": 3, "project": data.private_project2.pk})
results = helper_test_http_method(client, 'post', url, create_data, users)
assert results == [401, 403, 403, 201, 201]
def test_user_story_patch(client, data):
public_url = reverse('userstories-detail', kwargs={"pk": data.public_user_story.pk})
private_url1 = reverse('userstories-detail', kwargs={"pk": data.private_user_story1.pk})
private_url2 = reverse('userstories-detail', kwargs={"pk": data.private_user_story2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
patch_data = json.dumps({"subject": "test", "version": data.public_user_story.version})
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
assert results == [401, 403, 403, 200, 200]
patch_data = json.dumps({"subject": "test", "version": data.private_user_story1.version})
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
assert results == [401, 403, 403, 200, 200]
patch_data = json.dumps({"subject": "test", "version": data.private_user_story2.version})
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
assert results == [401, 403, 403, 200, 200]
def test_user_story_action_bulk_create(client, data):
url = reverse('userstories-bulk-create')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
bulk_data = json.dumps({"bulkStories": "test1\ntest2", "projectId": data.public_user_story.project.pk})
results = helper_test_http_method(client, 'post', url, bulk_data, users)
assert results == [401, 200, 200, 200, 200]
bulk_data = json.dumps({"bulkStories": "test1\ntest2", "projectId": data.private_user_story1.project.pk})
results = helper_test_http_method(client, 'post', url, bulk_data, users)
assert results == [401, 200, 200, 200, 200]
bulk_data = json.dumps({"bulkStories": "test1\ntest2", "projectId": data.private_user_story2.project.pk})
results = helper_test_http_method(client, 'post', url, bulk_data, users)
assert results == [401, 403, 403, 200, 200]
def test_user_story_action_bulk_update_order(client, data):
url = reverse('userstories-bulk-update-order')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
post_data = json.dumps({
"bulkStories": [(1,2)],
"projectId": data.public_project.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 204, 204]
post_data = json.dumps({
"bulkStories": [(1,2)],
"projectId": data.private_project1.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 204, 204]
post_data = json.dumps({
"bulkStories": [(1,2)],
"projectId": data.private_project2.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 204, 204]

View File

@ -0,0 +1,422 @@
import pytest
from django.core.urlresolvers import reverse
from rest_framework.renderers import JSONRenderer
from taiga.projects.wiki.serializers import WikiPageSerializer, WikiLinkSerializer
from taiga.projects.wiki.models import WikiPage, WikiLink
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
from taiga.projects.votes.services import add_vote
import json
pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.registered_user = f.UserFactory.create()
m.project_member_with_perms = f.UserFactory.create()
m.project_member_without_perms = f.UserFactory.create()
m.project_owner = f.UserFactory.create()
m.other_user = f.UserFactory.create()
m.public_project = f.ProjectFactory(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project1 = f.ProjectFactory(is_private=True,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
owner=m.project_owner)
m.private_project2 = f.ProjectFactory(is_private=True,
anon_permissions=[],
public_permissions=[],
owner=m.project_owner)
m.public_membership = f.MembershipFactory(project=m.public_project,
user=m.project_member_with_perms,
role__project=m.public_project,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
user=m.project_member_with_perms,
role__project=m.private_project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project1,
user=m.project_member_without_perms,
role__project=m.private_project1,
role__permissions=[])
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
user=m.project_member_with_perms,
role__project=m.private_project2,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.MembershipFactory(project=m.private_project2,
user=m.project_member_without_perms,
role__project=m.private_project2,
role__permissions=[])
m.public_wiki_page = f.WikiPageFactory(project=m.public_project)
m.private_wiki_page1 = f.WikiPageFactory(project=m.private_project1)
m.private_wiki_page2 = f.WikiPageFactory(project=m.private_project2)
m.public_wiki_link = f.WikiLinkFactory(project=m.public_project)
m.private_wiki_link1 = f.WikiLinkFactory(project=m.private_project1)
m.private_wiki_link2 = f.WikiLinkFactory(project=m.private_project2)
return m
def test_wiki_page_retrieve(client, data):
public_url = reverse('wiki-detail', kwargs={"pk": data.public_wiki_page.pk})
private_url1 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page1.pk})
private_url2 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_wiki_page_update(client, data):
public_url = reverse('wiki-detail', kwargs={"pk": data.public_wiki_page.pk})
private_url1 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page1.pk})
private_url2 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
wiki_page_data = WikiPageSerializer(data.public_wiki_page).data
wiki_page_data["content"] = "test"
wiki_page_data = JSONRenderer().render(wiki_page_data)
results = helper_test_http_method(client, 'put', public_url, wiki_page_data, users)
assert results == [401, 200, 200, 200, 200]
wiki_page_data = WikiPageSerializer(data.private_wiki_page1).data
wiki_page_data["content"] = "test"
wiki_page_data = JSONRenderer().render(wiki_page_data)
results = helper_test_http_method(client, 'put', private_url1, wiki_page_data, users)
assert results == [401, 200, 200, 200, 200]
wiki_page_data = WikiPageSerializer(data.private_wiki_page2).data
wiki_page_data["content"] = "test"
wiki_page_data = JSONRenderer().render(wiki_page_data)
results = helper_test_http_method(client, 'put', private_url2, wiki_page_data, users)
assert results == [401, 403, 403, 200, 200]
def test_wiki_page_delete(client, data):
public_url = reverse('wiki-detail', kwargs={"pk": data.public_wiki_page.pk})
private_url1 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page1.pk})
private_url2 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_wiki_page_list(client, data):
url = reverse('wiki-list')
response = client.get(url)
wiki_pages_data = json.loads(response.content.decode('utf-8'))
assert len(wiki_pages_data) == 2
assert response.status_code == 200
client.login(data.registered_user)
response = client.get(url)
wiki_pages_data = json.loads(response.content.decode('utf-8'))
assert len(wiki_pages_data) == 2
assert response.status_code == 200
client.login(data.project_member_with_perms)
response = client.get(url)
wiki_pages_data = json.loads(response.content.decode('utf-8'))
assert len(wiki_pages_data) == 3
assert response.status_code == 200
client.login(data.project_owner)
response = client.get(url)
wiki_pages_data = json.loads(response.content.decode('utf-8'))
assert len(wiki_pages_data) == 3
assert response.status_code == 200
def test_wiki_page_create(client, data):
url = reverse('wiki-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
create_data = json.dumps({
"content": "test",
"slug": "test",
"project": data.public_project.pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiPage.objects.all().delete())
assert results == [401, 201, 201, 201, 201]
create_data = json.dumps({
"content": "test",
"slug": "test",
"project": data.private_project1.pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiPage.objects.all().delete())
assert results == [401, 201, 201, 201, 201]
create_data = json.dumps({
"content": "test",
"slug": "test",
"project": data.private_project2.pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiPage.objects.all().delete())
assert results == [401, 403, 403, 201, 201]
def test_wiki_page_patch(client, data):
public_url = reverse('wiki-detail', kwargs={"pk": data.public_wiki_page.pk})
private_url1 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page1.pk})
private_url2 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
patch_data = json.dumps({"content": "test", "version": data.public_wiki_page.version})
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
assert results == [401, 200, 200, 200, 200]
patch_data = json.dumps({"content": "test", "version": data.private_wiki_page2.version})
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
assert results == [401, 200, 200, 200, 200]
patch_data = json.dumps({"content": "test", "version": data.private_wiki_page2.version})
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
assert results == [401, 403, 403, 200, 200]
def test_wiki_page_action_render(client, data):
url = reverse('wiki-render')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
post_data = json.dumps({"content": "test", "project_id": data.public_project.pk})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [200, 200, 200, 200, 200]
def test_wiki_link_retrieve(client, data):
public_url = reverse('wiki-links-detail', kwargs={"pk": data.public_wiki_link.pk})
private_url1 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link1.pk})
private_url2 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_wiki_link_update(client, data):
public_url = reverse('wiki-links-detail', kwargs={"pk": data.public_wiki_link.pk})
private_url1 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link1.pk})
private_url2 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
wiki_link_data = WikiLinkSerializer(data.public_wiki_link).data
wiki_link_data["title"] = "test"
wiki_link_data = JSONRenderer().render(wiki_link_data)
results = helper_test_http_method(client, 'put', public_url, wiki_link_data, users)
assert results == [401, 200, 200, 200, 200]
wiki_link_data = WikiLinkSerializer(data.private_wiki_link1).data
wiki_link_data["title"] = "test"
wiki_link_data = JSONRenderer().render(wiki_link_data)
results = helper_test_http_method(client, 'put', private_url1, wiki_link_data, users)
assert results == [401, 200, 200, 200, 200]
wiki_link_data = WikiLinkSerializer(data.private_wiki_link2).data
wiki_link_data["title"] = "test"
wiki_link_data = JSONRenderer().render(wiki_link_data)
results = helper_test_http_method(client, 'put', private_url2, wiki_link_data, users)
assert results == [401, 403, 403, 200, 200]
def test_wiki_link_delete(client, data):
public_url = reverse('wiki-links-detail', kwargs={"pk": data.public_wiki_link.pk})
private_url1 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link1.pk})
private_url2 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url1, None, users)
assert results == [401, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private_url2, None, users)
assert results == [401, 403, 403, 204]
def test_wiki_link_list(client, data):
url = reverse('wiki-links-list')
response = client.get(url)
wiki_links_data = json.loads(response.content.decode('utf-8'))
assert len(wiki_links_data) == 2
assert response.status_code == 200
client.login(data.registered_user)
response = client.get(url)
wiki_links_data = json.loads(response.content.decode('utf-8'))
assert len(wiki_links_data) == 2
assert response.status_code == 200
client.login(data.project_member_with_perms)
response = client.get(url)
wiki_links_data = json.loads(response.content.decode('utf-8'))
assert len(wiki_links_data) == 3
assert response.status_code == 200
client.login(data.project_owner)
response = client.get(url)
wiki_links_data = json.loads(response.content.decode('utf-8'))
assert len(wiki_links_data) == 3
assert response.status_code == 200
def test_wiki_link_create(client, data):
url = reverse('wiki-links-list')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
create_data = json.dumps({
"title": "test",
"href": "test",
"project": data.public_project.pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiLink.objects.all().delete())
assert results == [401, 201, 201, 201, 201]
create_data = json.dumps({
"title": "test",
"href": "test",
"project": data.private_project1.pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiLink.objects.all().delete())
assert results == [401, 201, 201, 201, 201]
create_data = json.dumps({
"title": "test",
"href": "test",
"project": data.private_project2.pk,
})
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiLink.objects.all().delete())
assert results == [401, 403, 403, 201, 201]
def test_wiki_link_patch(client, data):
public_url = reverse('wiki-links-detail', kwargs={"pk": data.public_wiki_link.pk})
private_url1 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link1.pk})
private_url2 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
patch_data = json.dumps({"title": "test"})
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
assert results == [401, 200, 200, 200, 200]
patch_data = json.dumps({"title": "test"})
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
assert results == [401, 200, 200, 200, 200]
patch_data = json.dumps({"title": "test"})
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -10,7 +10,7 @@ pytestmark = pytest.mark.django_db
def test_authentication(client): def test_authentication(client):
"User can't access an attachment if not authenticated" "User can't access an attachment if not authenticated"
attachment = f.AttachmentFactory.create() attachment = f.UserStoryAttachmentFactory.create()
url = reverse("attachment-url", kwargs={"pk": attachment.pk}) url = reverse("attachment-url", kwargs={"pk": attachment.pk})
response = client.get(url) response = client.get(url)
@ -20,7 +20,7 @@ def test_authentication(client):
def test_authorization(client): def test_authorization(client):
"User can't access an attachment if not authorized" "User can't access an attachment if not authorized"
attachment = f.AttachmentFactory.create() attachment = f.UserStoryAttachmentFactory.create()
user = f.UserFactory.create() user = f.UserFactory.create()
url = reverse("attachment-url", kwargs={"pk": attachment.pk}) url = reverse("attachment-url", kwargs={"pk": attachment.pk})
@ -34,7 +34,7 @@ def test_authorization(client):
@set_settings(IN_DEVELOPMENT_SERVER=True) @set_settings(IN_DEVELOPMENT_SERVER=True)
def test_attachment_redirect_in_devserver(client): def test_attachment_redirect_in_devserver(client):
"When accessing the attachment in devserver redirect to the real attachment url" "When accessing the attachment in devserver redirect to the real attachment url"
attachment = f.AttachmentFactory.create() attachment = f.UserStoryAttachmentFactory.create(attached_file="test")
url = reverse("attachment-url", kwargs={"pk": attachment.pk}) url = reverse("attachment-url", kwargs={"pk": attachment.pk})
@ -47,7 +47,7 @@ def test_attachment_redirect_in_devserver(client):
@set_settings(IN_DEVELOPMENT_SERVER=False) @set_settings(IN_DEVELOPMENT_SERVER=False)
def test_attachment_redirect(client): def test_attachment_redirect(client):
"When accessing the attachment redirect using X-Accel-Redirect header" "When accessing the attachment redirect using X-Accel-Redirect header"
attachment = f.AttachmentFactory.create() attachment = f.UserStoryAttachmentFactory.create()
url = reverse("attachment-url", kwargs={"pk": attachment.pk}) url = reverse("attachment-url", kwargs={"pk": attachment.pk})

View File

@ -0,0 +1,116 @@
import pytest
from taiga.permissions import service, permissions
from django.contrib.auth.models import AnonymousUser
from .. import factories
pytestmark = pytest.mark.django_db
def test_get_user_project_role():
user1 = factories.UserFactory()
user2 = factories.UserFactory()
project = factories.ProjectFactory()
role = factories.RoleFactory()
membership = factories.MembershipFactory(user=user1, project=project, role=role)
assert service._get_user_project_membership(user1, project) == membership
assert service._get_user_project_membership(user2, project) is None
def test_anon_get_user_project_permissions():
project = factories.ProjectFactory()
project.anon_permissions = ["test1"]
project.public_permissions = ["test2"]
assert service.get_user_project_permissions(AnonymousUser(), project) == set(["test1"])
def test_user_get_user_project_permissions_on_public_project():
user1 = factories.UserFactory()
project = factories.ProjectFactory()
project.anon_permissions = ["test1"]
project.public_permissions = ["test2"]
assert service.get_user_project_permissions(user1, project) == set(["test1", "test2"])
def test_user_get_user_project_permissions_on_private_project():
user1 = factories.UserFactory()
project = factories.ProjectFactory()
project.anon_permissions = ["test1"]
project.public_permissions = ["test2"]
project.is_private = True
assert service.get_user_project_permissions(user1, project) == set(["test1", "test2"])
def test_owner_get_user_project_permissions():
user1 = factories.UserFactory()
project = factories.ProjectFactory()
project.anon_permissions = ["test1"]
project.public_permissions = ["test2"]
project.owner = user1
role = factories.RoleFactory(permissions=["view_us"])
factories.MembershipFactory(user=user1, project=project, role=role)
expected_perms = set(
["test1", "test2"] +
[x[0] for x in permissions.OWNERS_PERMISSIONS] +
[x[0] for x in permissions.MEMBERS_PERMISSIONS]
)
assert service.get_user_project_permissions(user1, project) == expected_perms
def test_owner_member_get_user_project_permissions():
user1 = factories.UserFactory()
project = factories.ProjectFactory()
project.anon_permissions = ["test1"]
project.public_permissions = ["test2"]
role = factories.RoleFactory(permissions=["test3"])
factories.MembershipFactory(user=user1, project=project, role=role, is_owner=True)
expected_perms = set(
["test1", "test2", "test3"] +
[x[0] for x in permissions.OWNERS_PERMISSIONS]
)
assert service.get_user_project_permissions(user1, project) == expected_perms
def test_member_get_user_project_permissions():
user1 = factories.UserFactory()
project = factories.ProjectFactory()
project.anon_permissions = ["test1"]
project.public_permissions = ["test2"]
role = factories.RoleFactory(permissions=["test3"])
factories.MembershipFactory(user=user1, project=project, role=role)
assert service.get_user_project_permissions(user1, project) == set(["test1", "test2", "test3"])
def test_anon_user_has_perm():
project = factories.ProjectFactory()
project.anon_permissions = ["test"]
assert service.user_has_perm(AnonymousUser(), "test", project) == True
assert service.user_has_perm(AnonymousUser(), "fail", project) == False
def test_authenticated_user_has_perm_on_project():
user1 = factories.UserFactory()
project = factories.ProjectFactory()
project.public_permissions = ["test"]
assert service.user_has_perm(user1, "test", project) == True
assert service.user_has_perm(user1, "fail", project) == False
def test_authenticated_user_has_perm_on_project_related_object():
user1 = factories.UserFactory()
project = factories.ProjectFactory()
project.public_permissions = ["test"]
us = factories.UserStoryFactory(project=project)
assert service.user_has_perm(user1, "test", us) == True
assert service.user_has_perm(user1, "fail", us) == False
def test_authenticated_user_has_perm_on_invalid_object():
user1 = factories.UserFactory()
assert service.user_has_perm(user1, "test", user1) == False

View File

@ -28,9 +28,9 @@ pytestmark = pytest.mark.django_db
def test_archived_filter(client): def test_archived_filter(client):
user = f.UserFactory.create() user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user) project = f.ProjectFactory.create(owner=user)
membership = f.MembershipFactory.create(project=project, user=user) f.MembershipFactory.create(project=project, user=user)
userstory_1 = f.UserStoryFactory.create(project=project) f.UserStoryFactory.create(project=project)
user_story_2 = f.UserStoryFactory.create(is_archived=True, project=project) f.UserStoryFactory.create(is_archived=True, project=project)
client.login(user) client.login(user)

View File

@ -21,9 +21,21 @@ from django.core.urlresolvers import reverse
from .. import factories as f from .. import factories as f
from taiga.permissions.permissions import MEMBERS_PERMISSIONS
from tests.utils import disconnect_signals, reconnect_signals
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
def setup_module(module):
disconnect_signals()
def teardown_module(module):
reconnect_signals()
@pytest.fixture @pytest.fixture
def searches_initial_data(): def searches_initial_data():
m = type("InitialData", (object,), {})() m = type("InitialData", (object,), {})()
@ -31,12 +43,33 @@ def searches_initial_data():
m.project1 = f.ProjectFactory.create() m.project1 = f.ProjectFactory.create()
m.project2 = f.ProjectFactory.create() m.project2 = f.ProjectFactory.create()
m.member1 = f.MembershipFactory.create(project=m.project1) m.member1 = f.MembershipFactory(project=m.project1,
m.member2 = f.MembershipFactory.create(project=m.project1) role__project=m.project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
m.member2 = f.MembershipFactory(project=m.project1,
role__project=m.project1,
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
f.RoleFactory(project=m.project2)
m.points1 = f.PointsFactory(project=m.project1, value=None)
m.points2 = f.PointsFactory(project=m.project2, value=None)
m.role_points1 = f.RolePointsFactory.create(role=m.project1.roles.all()[0],
points=m.points1,
user_story__project=m.project1)
m.role_points2 = f.RolePointsFactory.create(role=m.project1.roles.all()[0],
points=m.points1,
user_story__project=m.project1,
user_story__description="Back to the future")
m.role_points3 = f.RolePointsFactory.create(role=m.project2.roles.all()[0],
points=m.points2,
user_story__project=m.project2)
m.us1 = m.role_points1.user_story
m.us2 = m.role_points2.user_story
m.us3 = m.role_points3.user_story
m.us1 = f.UserStoryFactory.create(project=m.project1)
m.us2 = f.UserStoryFactory.create(project=m.project1, description="Back to the future")
m.us3 = f.UserStoryFactory.create(project=m.project2)
m.tsk1 = f.TaskFactory.create(project=m.project2) m.tsk1 = f.TaskFactory.create(project=m.project2)
m.tsk2 = f.TaskFactory.create(project=m.project1) m.tsk2 = f.TaskFactory.create(project=m.project1)
@ -73,7 +106,8 @@ def test_search_all_objects_in_project_is_not_mine(client, searches_initial_data
client.login(data.member1.user) client.login(data.member1.user)
response = client.get(reverse("search-list"), {"project": data.project2.id}) response = client.get(reverse("search-list"), {"project": data.project2.id})
assert response.status_code == 403 assert response.status_code == 200
assert response.data["count"] == 0
def test_search_text_query_in_my_project(client, searches_initial_data): def test_search_text_query_in_my_project(client, searches_initial_data):

View File

@ -75,7 +75,7 @@ def test_list_project_fans(client):
user = f.UserFactory.create() user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user) project = f.ProjectFactory.create(owner=user)
fan = f.VoteFactory.create(content_object=project) fan = f.VoteFactory.create(content_object=project)
url = reverse("project-fans-list", args=(project.id,)) url = reverse("projects-fans", args=(project.id,))
client.login(user) client.login(user)
response = client.get(url) response = client.get(url)
@ -84,23 +84,10 @@ def test_list_project_fans(client):
assert response.data[0]['id'] == fan.user.id assert response.data[0]['id'] == fan.user.id
def test_get_project_fan(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
fan = f.VoteFactory.create(content_object=project)
url = reverse("project-fans-detail", args=(project.id, fan.user.id))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['id'] == fan.user.id
def test_list_user_starred_projects(client): def test_list_user_starred_projects(client):
user = f.UserFactory.create() user = f.UserFactory.create()
project = f.ProjectFactory() project = f.ProjectFactory()
url = reverse("user-starred-list", args=(user.id,)) url = reverse("users-starred", args=(user.id,))
f.VoteFactory.create(user=user, content_object=project) f.VoteFactory.create(user=user, content_object=project)
client.login(user) client.login(user)
@ -110,19 +97,6 @@ def test_list_user_starred_projects(client):
assert response.data[0]['id'] == project.id assert response.data[0]['id'] == project.id
def test_get_user_starred_project(client):
user = f.UserFactory.create()
project = f.ProjectFactory()
url = reverse("user-starred-detail", args=(user.id, project.id))
f.VoteFactory.create(user=user, content_object=project)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['id'] == project.id
def test_get_project_stars(client): def test_get_project_stars(client):
user = f.UserFactory.create() user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user) project = f.ProjectFactory.create(owner=user)

View File

@ -25,17 +25,18 @@ from .. import factories
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
def test_list_userstories(client): def test_list_userstorage(client):
user1 = factories.UserFactory() user1 = factories.UserFactory()
user2 = factories.UserFactory() user2 = factories.UserFactory()
storage11 = factories.StorageEntryFactory(owner=user1) storage11 = factories.StorageEntryFactory(owner=user1)
storage12 = factories.StorageEntryFactory(owner=user1) factories.StorageEntryFactory(owner=user1)
storage13 = factories.StorageEntryFactory(owner=user1) storage13 = factories.StorageEntryFactory(owner=user1)
storage21 = factories.StorageEntryFactory(owner=user2) factories.StorageEntryFactory(owner=user2)
# List by anonumous user # List by anonumous user
response = client.get(reverse("user-storage-list")) response = client.get(reverse("user-storage-list"))
assert response.status_code == 401 assert response.status_code == 200
assert len(response.data) == 0
# List own entries # List own entries
client.login(username=user1.username, password=user1.username) client.login(username=user1.username, password=user1.username)
@ -57,8 +58,6 @@ def test_list_userstories(client):
assert response.status_code == 200 assert response.status_code == 200
assert len(response.data) == 2 assert len(response.data) == 2
client.logout()
def test_view_storage_entries(client): def test_view_storage_entries(client):
user1 = factories.UserFactory() user1 = factories.UserFactory()
@ -84,12 +83,9 @@ def test_view_storage_entries(client):
response = client.get(reverse("user-storage-detail", args=["foobar"])) response = client.get(reverse("user-storage-detail", args=["foobar"]))
assert response.status_code == 404 assert response.status_code == 404
client.logout()
def test_create_entries(client): def test_create_entries(client):
user1 = factories.UserFactory() user1 = factories.UserFactory()
user2 = factories.UserFactory()
storage11 = factories.StorageEntryFactory(owner=user1) storage11 = factories.StorageEntryFactory(owner=user1)
form = {"key": "foo", form = {"key": "foo",
@ -119,12 +115,9 @@ def test_create_entries(client):
response = client.post(reverse("user-storage-list"), error_form) response = client.post(reverse("user-storage-list"), error_form)
assert response.status_code == 400 assert response.status_code == 400
client.logout()
def test_update_entries(client): def test_update_entries(client):
user1 = factories.UserFactory() user1 = factories.UserFactory()
user2 = factories.UserFactory()
storage11 = factories.StorageEntryFactory(owner=user1) storage11 = factories.StorageEntryFactory(owner=user1)
# Update by anonymous user # Update by anonymous user
@ -158,9 +151,6 @@ def test_update_entries(client):
assert response.status_code == 200 assert response.status_code == 200
assert response.data["value"] == form["value"] assert response.data["value"] == form["value"]
client.logout()
def test_delete_storage_entry(client): def test_delete_storage_entry(client):
user1 = factories.UserFactory() user1 = factories.UserFactory()
@ -186,6 +176,3 @@ def test_delete_storage_entry(client):
client.login(username=user2.username, password=user2.username) client.login(username=user2.username, password=user2.username)
response = client.delete(reverse("user-storage-detail", args=[storage11.key])) response = client.delete(reverse("user-storage-detail", args=[storage11.key]))
assert response.status_code == 404 assert response.status_code == 404
client.logout()

View File

@ -0,0 +1,20 @@
from taiga.base.api.permissions import (PermissionComponent,
AllowAny as TruePermissionComponent,
DenyAll as FalsePermissionComponent)
import pytest
def test_permission_component_composition():
assert (TruePermissionComponent() | TruePermissionComponent()).check_permissions(None, None, None)
assert (TruePermissionComponent() | FalsePermissionComponent()).check_permissions(None, None, None)
assert (FalsePermissionComponent() | TruePermissionComponent()).check_permissions(None, None, None)
assert not (FalsePermissionComponent() | FalsePermissionComponent()).check_permissions(None, None, None)
assert (TruePermissionComponent() & TruePermissionComponent()).check_permissions(None, None, None)
assert not (TruePermissionComponent() & FalsePermissionComponent()).check_permissions(None, None, None)
assert not (FalsePermissionComponent() & TruePermissionComponent()).check_permissions(None, None, None)
assert not (FalsePermissionComponent() & FalsePermissionComponent()).check_permissions(None, None, None)
assert (~FalsePermissionComponent()).check_permissions(None, None, None)
assert not (~TruePermissionComponent()).check_permissions(None, None, None)

View File

@ -0,0 +1,11 @@
from taiga.permissions import service
from taiga.users.models import Role
import pytest
def test_role_has_perm():
role = Role()
role.permissions = ["test"]
assert service.role_has_perm(role, "test")
assert service.role_has_perm(role, "false") == False

View File

@ -15,6 +15,7 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import functools import functools
import json
from django.conf import settings from django.conf import settings
from django.db.models import signals from django.db.models import signals
@ -86,3 +87,36 @@ class SettingsTestCase(object):
def teardown_class(cls): def teardown_class(cls):
override_settings(cls.ORIGINAL_SETTINGS) override_settings(cls.ORIGINAL_SETTINGS)
cls.OVERRIDE_SETTINGS.clear() cls.OVERRIDE_SETTINGS.clear()
def _helper_test_http_method_responses(client, method, url, data, users, after_each_request=None):
results = []
for user in users:
if user is None:
client.logout()
else:
client.login(user)
if data:
response = getattr(client, method)(url, data, content_type="application/json")
else:
response = getattr(client, method)(url)
if response.status_code == 400:
print(response.content)
results.append(response)
if after_each_request is not None:
after_each_request()
return results
def helper_test_http_method(client, method, url, data, users, after_each_request=None):
responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request)
return list(map(lambda r: r.status_code, responses))
def helper_test_http_method_and_count(client, method, url, data, users, after_each_request=None):
responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request)
return list(map(lambda r: (r.status_code, len(json.loads(r.content.decode('utf-8')))), responses))
def helper_test_http_method_and_keys(client, method, url, data, users, after_each_request=None):
responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request)
return list(map(lambda r: (r.status_code, set(json.loads(r.content.decode('utf-8')).keys())), responses))