1
1
from django .contrib .auth .models import User
2
2
from django .shortcuts import get_object_or_404
3
- from rest_framework .exceptions import PermissionDenied
3
+ from rest_framework .exceptions import NotFound
4
4
5
+ from readthedocs .builds .models import Version
5
6
from readthedocs .projects .models import Project
6
7
7
8
8
- class NestedParentProjectMixin :
9
+ class NestedParentObjectMixin :
9
10
10
11
# Lookup names defined on ``readthedocs/api/v3/urls.py`` when defining the
11
12
# mapping between URLs and views through the router.
12
- LOOKUP_NAMES = [
13
+ PROJECT_LOOKUP_NAMES = [
13
14
'project__slug' ,
14
15
'projects__slug' ,
15
16
'superprojects__parent__slug' ,
16
17
'main_language_project__slug' ,
17
18
]
18
19
19
- def _get_parent_project (self ):
20
+ VERSION_LOOKUP_NAMES = [
21
+ 'version__slug' ,
22
+ ]
23
+
24
+ def _get_parent_object (self , model , lookup_names ):
20
25
project_slug = None
21
26
query_dict = self .get_parents_query_dict ()
22
- for lookup in self . LOOKUP_NAMES :
27
+ for lookup in lookup_names :
23
28
value = query_dict .get (lookup )
24
29
if value :
25
- project_slug = value
30
+ slug = value
26
31
break
27
32
28
- return get_object_or_404 (Project , slug = project_slug )
33
+ return get_object_or_404 (model , slug = slug )
34
+
35
+ def _get_parent_project (self ):
36
+ return self ._get_parent_object (Project , self .PROJECT_LOOKUP_NAMES )
29
37
38
+ def _get_parent_version (self ):
39
+ return self ._get_parent_object (Version , self .VERSION_LOOKUP_NAMES )
30
40
31
- class APIAuthMixin (NestedParentProjectMixin ):
41
+
42
+ class APIAuthMixin (NestedParentObjectMixin ):
32
43
33
44
"""
34
45
Mixin to define queryset permissions for ViewSet only in one place.
@@ -37,40 +48,58 @@ class APIAuthMixin(NestedParentProjectMixin):
37
48
required. In that case, an specific mixin for that case should be defined.
38
49
"""
39
50
51
+ def detail_objects (self , queryset , user ):
52
+ # Filter results by user
53
+ # NOTE: we don't override the manager in User model, so we don't have
54
+ # ``.api`` method there
55
+ if self .model is not User :
56
+ queryset = queryset .api (user = user )
57
+
58
+ return queryset
59
+
60
+ def listing_objects (self , queryset , user ):
61
+ project = self ._get_parent_project ()
62
+ if self .has_admin_permission (user , project ):
63
+ return queryset
64
+
65
+ def has_admin_permission (self , user , project ):
66
+ if project in self .admin_projects (user ):
67
+ return True
68
+
69
+ return False
70
+
71
+ def admin_projects (self , user ):
72
+ return Project .objects .for_admin_user (user = user )
73
+
40
74
def get_queryset (self ):
41
75
"""
42
76
Filter results based on user permissions.
43
77
44
- 1. filters by parent ``project_slug `` (NestedViewSetMixin).
45
- 2. return those results if it's a detail view.
46
- 3. if it's a list view, it checks if the user is admin of the parent
47
- object (project) and return the same results.
48
- 4. raise a ``PermissionDenied `` exception if the user is not an admin.
78
+ 1. returns ``Projects`` where the user is admin if ``/projects/ `` is hit
79
+ 2. filters by parent ``project_slug`` (NestedViewSetMixin)
80
+ 2. returns ``detail_objects`` results if it's a detail view
81
+ 3. returns ``listing_objects`` results if it's a listing view
82
+ 4. raise a ``NotFound `` exception otherwise
49
83
"""
50
84
85
+ # Allow hitting ``/api/v3/projects/`` to list their own projects
86
+ if self .basename == 'projects' and self .action == 'list' :
87
+ # We force returning ``Project`` objects here because it's under the
88
+ # ``projects`` view. This could be moved to a specific
89
+ # ``get_queryset`` in the view.
90
+ return self .admin_projects (self .request .user )
91
+
51
92
# NOTE: ``super().get_queryset`` produces the filter by ``NestedViewSetMixin``
52
93
# we need to have defined the class attribute as ``queryset = Model.objects.all()``
53
94
queryset = super ().get_queryset ()
54
95
55
- # Filter results by user
56
- # NOTE: we don't override the manager in User model, so we don't have
57
- # ``.api`` method there
58
- if self .model is not User :
59
- queryset = queryset .api (user = self .request .user )
60
-
61
96
# Detail requests are public
62
97
if self .detail :
63
- return queryset
64
-
65
- allowed_projects = Project .objects .for_admin_user (user = self .request .user )
66
-
67
- # Allow hitting ``/api/v3/projects/`` to list their own projects
68
- if self .basename == 'projects' and self .action == 'list' :
69
- return allowed_projects
98
+ return self .detail_objects (queryset , self .request .user )
70
99
71
100
# List view are only allowed if user is owner of parent project
72
- project = self ._get_parent_project ( )
73
- if project in allowed_projects :
74
- return queryset
101
+ listing_objects = self .listing_objects ( queryset , self . request . user )
102
+ if listing_objects :
103
+ return listing_objects
75
104
76
- raise PermissionDenied
105
+ raise NotFound
0 commit comments