Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix preprint permissions #214

Open
wants to merge 2 commits into
base: feature/preprints-affiliations
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions api/preprints/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from addons.osfstorage.models import OsfStorageFolder
from osf.utils.workflows import DefaultStates
from osf.utils import permissions as osf_permissions
from osf.utils.permissions import ADMIN


class PreprintPublishedOrAdmin(permissions.BasePermission):
Expand Down Expand Up @@ -53,6 +54,46 @@ def has_object_permission(self, request, view, obj):
return True


class PreprintCitationPermissions(permissions.BasePermission):

acceptable_models = (Preprint,)

def has_object_permission(self, request, view, obj):
assert_resource_type(obj, self.acceptable_models)
auth = get_user_auth(request)
if not auth.user:
return obj.verified_publishable

if obj.is_published:
return True

if obj.is_public and obj.has_submitted_preprint:
return True

if obj.deleted:
return False

if obj.is_preprint_orphan:
return False

if obj.is_retracted and not obj.ever_public:
return False

if obj.verified_publishable:
return True

if obj.is_public and auth.user.has_perm('view_submissions', obj.provider):
return True

if obj.has_permission(auth.user, ADMIN):
return True

if obj.is_contributor(auth.user) and obj.has_submitted_preprint:
return True

return False


class ContributorDetailPermissions(PreprintPublishedOrAdmin):
"""Permissions for preprint contributor detail page."""

Expand Down
2 changes: 1 addition & 1 deletion api/preprints/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
re_path(r'^(?P<preprint_id>\w+)/relationships/node/$', views.PreprintNodeRelationship.as_view(), name=views.PreprintNodeRelationship.view_name),
re_path(r'^(?P<preprint_id>\w+)/relationships/subjects/$', views.PreprintSubjectsRelationship.as_view(), name=views.PreprintSubjectsRelationship.view_name),
re_path(r'^(?P<preprint_id>\w+)/review_actions/$', views.PreprintActionList.as_view(), name=views.PreprintActionList.view_name),
re_path(r'^(?P<preprint_id>\w+)/requests/$', views.PreprintRequestListCreate.as_view(), name=views.PreprintRequestListCreate.view_name),
re_path(r'^(?P<preprint_id>\w+)/requests/$', views.PreprintRequestList.as_view(), name=views.PreprintRequestList.view_name),
re_path(r'^(?P<preprint_id>\w+)/subjects/$', views.PreprintSubjectsList.as_view(), name=views.PreprintSubjectsList.view_name),
]
73 changes: 31 additions & 42 deletions api/preprints/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,19 @@
AdminOrPublic,
ContributorDetailPermissions,
PreprintFilesPermissions,
PreprintCitationPermissions
)
from api.nodes.permissions import (
ContributorOrPublic,
)
from api.requests.permissions import PreprintRequestPermission
from api.requests.serializers import PreprintRequestSerializer, PreprintRequestCreateSerializer
from api.requests.serializers import PreprintRequestSerializer
from api.requests.views import PreprintRequestMixin
from api.subjects.views import BaseResourceSubjectsList, SubjectRelationshipBaseView
from api.base.metrics import PreprintMetricsViewMixin
from osf.metrics import PreprintDownload, PreprintView


class PreprintMixin(NodeMixin):
serializer_class = PreprintSerializer
preprint_lookup_url_kwarg = 'preprint_id'
Expand All @@ -84,6 +86,7 @@ def get_preprint(self, check_object_permissions=True, ignore_404=False):

return preprint


class PreprintList(PreprintMetricsViewMixin, JSONAPIBaseView, generics.ListCreateAPIView, PreprintFilterMixin):
"""The documentation for this endpoint can be found [here](https://developer.osf.io/#operation/preprints_list).
"""
Expand Down Expand Up @@ -219,11 +222,13 @@ def get_object(self):
preprint = self.get_preprint()
auth = get_user_auth(self.request)
type_ = 'linked_preprint_nodes' if Version(self.request.version) < Version('2.13') else 'nodes'
obj = {
'data': {'id': preprint.node._id, 'type': type_} if preprint.node and preprint.node.can_view(auth) else None,
return {
'data': {
'id': preprint.node._id,
'type': type_
} if preprint.node and preprint.node.can_view(auth) else None,
'self': preprint,
}
return obj


class PreprintCitationDetail(JSONAPIBaseView, generics.RetrieveAPIView, PreprintMixin):
Expand All @@ -232,6 +237,7 @@ class PreprintCitationDetail(JSONAPIBaseView, generics.RetrieveAPIView, Preprint
permission_classes = (
drf_permissions.IsAuthenticatedOrReadOnly,
base_permissions.TokenHasScope,
PreprintCitationPermissions
)

required_read_scopes = [CoreScopes.PREPRINT_CITATIONS_READ]
Expand All @@ -242,13 +248,7 @@ class PreprintCitationDetail(JSONAPIBaseView, generics.RetrieveAPIView, Preprint
view_name = 'preprint-citation'

def get_object(self):
preprint = self.get_preprint()
auth = get_user_auth(self.request)

if preprint.can_view(auth):
return preprint.csl

raise PermissionDenied if auth.user else NotAuthenticated
return self.get_preprint().csl


class PreprintCitationStyleDetail(JSONAPIBaseView, generics.RetrieveAPIView, PreprintMixin):
Expand All @@ -257,6 +257,7 @@ class PreprintCitationStyleDetail(JSONAPIBaseView, generics.RetrieveAPIView, Pre
permission_classes = (
drf_permissions.IsAuthenticatedOrReadOnly,
base_permissions.TokenHasScope,
PreprintCitationPermissions
)

required_read_scopes = [CoreScopes.PREPRINT_CITATIONS_READ]
Expand All @@ -268,19 +269,18 @@ class PreprintCitationStyleDetail(JSONAPIBaseView, generics.RetrieveAPIView, Pre

def get_object(self):
preprint = self.get_preprint()
auth = get_user_auth(self.request)
style = self.kwargs.get('style_id')

if preprint.can_view(auth):
try:
citation = render_citation(node=preprint, style=style)
except ValueError as err: # style requested could not be found
csl_name = re.findall(r'[a-zA-Z]+\.csl', str(err))[0]
raise NotFound(f'{csl_name} is not a known style.')

return {'citation': citation, 'id': style}

raise PermissionDenied if auth.user else NotAuthenticated
try:
citation = render_citation(node=preprint, style=style)
except ValueError as err: # style requested could not be found
csl_name = re.findall(r'[a-zA-Z]+\.csl', str(err))[0]
raise NotFound(f'{csl_name} is not a known style.')

return {
'citation': citation,
'id': style
}


class PreprintIdentifierList(IdentifierList, PreprintMixin):
Expand Down Expand Up @@ -332,8 +332,8 @@ class PreprintIdentifierList(IdentifierList, PreprintMixin):
view_name = 'identifier-list'

# overrides IdentifierList
def get_object(self, check_object_permissions=True):
return self.get_preprint(check_object_permissions=check_object_permissions)
def get_object(self):
return self.get_preprint()


class PreprintContributorsList(NodeContributorsList, PreprintMixin):
Expand All @@ -354,8 +354,7 @@ class PreprintContributorsList(NodeContributorsList, PreprintMixin):
serializer_class = PreprintContributorsSerializer

def get_default_queryset(self):
preprint = self.get_preprint()
return preprint.preprintcontributor_set.all().prefetch_related('user__guids')
return self.get_preprint().preprintcontributor_set.all().prefetch_related('user__guids')

# overrides NodeContributorsList
def get_serializer_class(self):
Expand All @@ -370,7 +369,7 @@ def get_serializer_class(self):
return PreprintContributorsSerializer

def get_resource(self):
return self.get_preprint(ignore_404=True)
return self.get_preprint()

# Overrides NodeContributorsList
def get_serializer_context(self):
Expand Down Expand Up @@ -402,8 +401,6 @@ def get_resource(self):
def get_object(self):
preprint = self.get_preprint()
user = self.get_user()
# May raise a permission denied
self.check_object_permissions(self.request, user)
try:
return preprint.preprintcontributor_set.get(user=user)
except PreprintContributor.DoesNotExist:
Expand Down Expand Up @@ -474,17 +471,15 @@ class PreprintSubjectsRelationship(SubjectRelationshipBaseView, PreprintMixin):
view_category = 'preprints'
view_name = 'preprint-relationships-subjects'

def get_resource(self, check_object_permissions=True):
return self.get_preprint(check_object_permissions=check_object_permissions)
def get_resource(self):
return self.get_preprint()

def get_object(self):
resource = self.get_resource(check_object_permissions=False)
obj = {
resource = self.get_resource()
return {
'data': resource.subjects.all(),
'self': resource,
}
self.check_object_permissions(self.request, resource)
return obj


class PreprintActionList(JSONAPIBaseView, generics.ListCreateAPIView, ListFilterMixin, PreprintMixin):
Expand Down Expand Up @@ -619,7 +614,7 @@ def get_resource(self):
return get_object_or_error(Preprint, self.kwargs['preprint_id'], self.request)


class PreprintRequestListCreate(JSONAPIBaseView, generics.ListCreateAPIView, ListFilterMixin, PreprintRequestMixin):
class PreprintRequestList(JSONAPIBaseView, generics.ListCreateAPIView, ListFilterMixin, PreprintRequestMixin):
permission_classes = (
drf_permissions.IsAuthenticatedOrReadOnly,
base_permissions.TokenHasScope,
Expand All @@ -636,12 +631,6 @@ class PreprintRequestListCreate(JSONAPIBaseView, generics.ListCreateAPIView, Lis
view_category = 'preprint-requests'
view_name = 'preprint-request-list'

def get_serializer_class(self):
if self.request.method == 'POST':
return PreprintRequestCreateSerializer
else:
return PreprintRequestSerializer

def get_default_queryset(self):
return self.get_target().requests.all()

Expand Down
27 changes: 14 additions & 13 deletions api/requests/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def has_object_permission(self, request, view, obj):
class PreprintRequestPermission(drf_permissions.BasePermission):
def has_object_permission(self, request, view, obj):
auth = get_user_auth(request)

if auth.user is None:
return False

Expand All @@ -69,30 +70,30 @@ def has_object_permission(self, request, view, obj):
elif isinstance(obj, PreprintRequestableMixin):
target = obj
preprint = obj.target
# Creating a Request is "submitting"
trigger = request.data.get('trigger', DefaultTriggers.SUBMIT.value if request.method not in drf_permissions.SAFE_METHODS else None)
trigger = request.data.get(
'trigger',
DefaultTriggers.SUBMIT.value if request.method not in drf_permissions.SAFE_METHODS else None
)
elif isinstance(obj, Preprint):
preprint = obj
trigger = DefaultTriggers.SUBMIT.value if request.method not in drf_permissions.SAFE_METHODS else None
else:
raise ValueError(f'Not a request-related model: {obj}')

is_requester = target is not None and target.creator == auth.user or trigger == DefaultTriggers.SUBMIT.value
is_requester = (target is not None and target.creator == auth.user) or trigger == DefaultTriggers.SUBMIT.value
is_preprint_admin = preprint.has_permission(auth.user, osf_permissions.ADMIN)
is_moderator = auth.user.has_perm('withdraw_submissions', preprint.provider)
has_view_permission = is_requester or is_preprint_admin or is_moderator

if request.method in drf_permissions.SAFE_METHODS:
# Requesters, moderators, and preprint admins can view actions
return has_view_permission
else:
if not has_view_permission:
return False

if trigger in [DefaultTriggers.ACCEPT.value, DefaultTriggers.REJECT.value]:
# Only moderators can approve or reject requests
return is_moderator
if trigger in [DefaultTriggers.EDIT_COMMENT.value, DefaultTriggers.SUBMIT.value]:
# Requesters may edit their comment or submit their request
return is_requester
if not has_view_permission:
return False

if trigger in [DefaultTriggers.ACCEPT.value, DefaultTriggers.REJECT.value]:
return is_moderator
if trigger in [DefaultTriggers.EDIT_COMMENT.value, DefaultTriggers.SUBMIT.value]:
return is_requester

return False
17 changes: 12 additions & 5 deletions api/requests/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,10 @@ def create(self, validated_data):
node_request.run_submit(auth.user)
return node_request


class PreprintRequestSerializer(RequestSerializer):
class Meta:
type_ = 'preprint-requests'

request_type = ser.ChoiceField(required=True, choices=RequestTypes.choices())

target = RelationshipField(
read_only=True,
Expand All @@ -143,10 +144,16 @@ class Meta:
)

def get_target_url(self, obj):
return absolute_reverse('preprints:preprint-detail', kwargs={'preprint_id': obj.target._id, 'version': self.context['request'].parser_context['kwargs']['version']})
return absolute_reverse(
'preprints:preprint-detail',
kwargs={
'preprint_id': obj.target._id,
'version': self.context['request'].parser_context['kwargs']['version']
}
)

class PreprintRequestCreateSerializer(PreprintRequestSerializer):
request_type = ser.ChoiceField(required=True, choices=RequestTypes.choices())
class Meta:
type_ = 'preprint-requests'

def create(self, validated_data):
auth = get_user_auth(self.context['request'])
Expand Down
Loading