Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bcc4ebe

Browse files
committed
Fix #327; introduced PERMISSIONS setting
1 parent 8e36113 commit bcc4ebe

6 files changed

Lines changed: 93 additions & 23 deletions

File tree

CHANGELOG.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ Change Log
55
This document records all notable changes to djoser.
66
This project adheres to `Semantic Versioning <http://semver.org/>`_.
77

8+
---------------------
9+
`1.4.0`_ (2019-01-09)
10+
---------------------
11+
12+
* Introduced new framework for setting default permissions for certain views.
13+
See :ref:`documentation<view-permission-settings>`.
14+
* Fix permissions regression introduced in 1.3.2 thanks to new permissions framework.
15+
Default permission for user-list view set to read-only, like in 1.3.2
16+
(defaults to read-only like in 1.3.2).
17+
818
---------------------
919
`1.3.2`_ (2018-12-05)
1020
---------------------
@@ -288,3 +298,4 @@ few bugfixes / documentation updates. List of changes:
288298
.. _1.3.0: https://github.com/sunscrapers/djoser/compare/1.2.0...1.3.0
289299
.. _1.3.1: https://github.com/sunscrapers/djoser/compare/1.3.0...1.3.1
290300
.. _1.3.2: https://github.com/sunscrapers/djoser/compare/1.3.1...1.3.2
301+
.. _1.3.3: https://github.com/sunscrapers/djoser/compare/1.3.2...1.3.3

djoser/conf.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
class ObjDict(dict):
1414
def __getattribute__(self, item):
1515
try:
16-
if isinstance(self[item], str):
17-
self[item] = import_string(self[item])
18-
value = self[item]
16+
val = self[item]
17+
if isinstance(val, str):
18+
val = import_string(val)
19+
elif isinstance(val, (list, tuple)):
20+
val = [import_string(v) if type(v) is str else v for v in val]
21+
self[item] = val
1922
except KeyError:
20-
value = super(ObjDict, self).__getattribute__(item)
23+
val = super(ObjDict, self).__getattribute__(item)
2124

22-
return value
25+
return val
2326

2427

2528
default_settings = {
@@ -71,6 +74,19 @@ def __getattribute__(self, item):
7174
'USER_EMAIL_FIELD_NAME': 'email',
7275
'SOCIAL_AUTH_TOKEN_STRATEGY': 'djoser.social.token.jwt.TokenStrategy',
7376
'SOCIAL_AUTH_ALLOWED_REDIRECT_URIS': [],
77+
'PERMISSIONS': ObjDict({
78+
'activation': ['rest_framework.permissions.AllowAny'],
79+
'password_reset': ['rest_framework.permissions.AllowAny'],
80+
'password_reset_confirm': ['rest_framework.permissions.AllowAny'],
81+
'set_password': ['djoser.permissions.CurrentUserOrAdmin'],
82+
'set_username': ['rest_framework.permissions.IsAuthenticated'],
83+
'user_create': ['rest_framework.permissions.AllowAny'],
84+
'user_delete': ['djoser.permissions.CurrentUserOrAdmin'],
85+
'user': ['djoser.permissions.CurrentUserOrAdminOrReadOnly'],
86+
'user_list': ['djoser.permissions.CurrentUserOrAdminOrReadOnly'],
87+
'token_create': ['rest_framework.permissions.AllowAny'],
88+
'token_destroy': ['rest_framework.permissions.IsAuthenticated'],
89+
}),
7490
}
7591

7692
SETTINGS_TO_IMPORT = ['TOKEN_MODEL', 'SOCIAL_AUTH_TOKEN_STRATEGY']

djoser/permissions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
from rest_framework import permissions
2+
from rest_framework.permissions import SAFE_METHODS
23

34

45
class CurrentUserOrAdmin(permissions.IsAuthenticated):
56
def has_object_permission(self, request, view, obj):
67
user = request.user
78
return user.is_staff or obj.pk == user.pk
9+
10+
11+
class CurrentUserOrAdminOrReadOnly(permissions.IsAuthenticated):
12+
def has_object_permission(self, request, view, obj):
13+
user = request.user
14+
if type(obj) == type(user) and obj == user:
15+
return True
16+
return request.method in SAFE_METHODS or user.is_staff

djoser/views.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from djoser import utils, signals
1111
from djoser.compat import get_user_email, get_user_email_field_name
1212
from djoser.conf import settings
13-
from djoser.permissions import CurrentUserOrAdmin
13+
1414

1515
User = get_user_model()
1616

@@ -66,7 +66,7 @@ class UserCreateView(generics.CreateAPIView):
6666
Use this endpoint to register new user.
6767
"""
6868
serializer_class = settings.SERIALIZERS.user_create
69-
permission_classes = [permissions.AllowAny]
69+
permission_classes = settings.PERMISSIONS.user_create
7070

7171
def perform_create(self, serializer):
7272
user = serializer.save()
@@ -87,7 +87,7 @@ class UserDeleteView(generics.CreateAPIView):
8787
Use this endpoint to remove actually authenticated user
8888
"""
8989
serializer_class = settings.SERIALIZERS.user_delete
90-
permission_classes = [permissions.IsAuthenticated]
90+
permission_classes = settings.PERMISSIONS.user_delete
9191

9292
def get_object(self):
9393
return self.request.user
@@ -108,7 +108,7 @@ class TokenCreateView(utils.ActionViewMixin, generics.GenericAPIView):
108108
Use this endpoint to obtain user authentication token.
109109
"""
110110
serializer_class = settings.SERIALIZERS.token_create
111-
permission_classes = [permissions.AllowAny]
111+
permission_classes = settings.PERMISSIONS.token_create
112112

113113
def _action(self, serializer):
114114
token = utils.login_user(self.request, serializer.user)
@@ -123,7 +123,7 @@ class TokenDestroyView(views.APIView):
123123
"""
124124
Use this endpoint to logout user (remove user authentication token).
125125
"""
126-
permission_classes = [permissions.IsAuthenticated]
126+
permission_classes = settings.PERMISSIONS.token_destroy
127127

128128
def post(self, request):
129129
utils.logout_user(request)
@@ -135,7 +135,7 @@ class PasswordResetView(utils.ActionViewMixin, generics.GenericAPIView):
135135
Use this endpoint to send email to user with password reset link.
136136
"""
137137
serializer_class = settings.SERIALIZERS.password_reset
138-
permission_classes = [permissions.AllowAny]
138+
permission_classes = settings.PERMISSIONS.password_reset
139139

140140
_users = None
141141

@@ -165,7 +165,7 @@ class SetPasswordView(utils.ActionViewMixin, generics.GenericAPIView):
165165
"""
166166
Use this endpoint to change user password.
167167
"""
168-
permission_classes = [permissions.IsAuthenticated]
168+
permission_classes = settings.PERMISSIONS.set_password
169169

170170
def get_serializer_class(self):
171171
if settings.SET_PASSWORD_RETYPE:
@@ -186,7 +186,7 @@ class PasswordResetConfirmView(utils.ActionViewMixin, generics.GenericAPIView):
186186
"""
187187
Use this endpoint to finish reset password process.
188188
"""
189-
permission_classes = [permissions.AllowAny]
189+
permission_classes = settings.PERMISSIONS.password_reset_confirm
190190
token_generator = default_token_generator
191191

192192
def get_serializer_class(self):
@@ -207,7 +207,7 @@ class ActivationView(utils.ActionViewMixin, generics.GenericAPIView):
207207
Use this endpoint to activate user account.
208208
"""
209209
serializer_class = settings.SERIALIZERS.activation
210-
permission_classes = [permissions.AllowAny]
210+
permission_classes = settings.PERMISSIONS.activation
211211
token_generator = default_token_generator
212212

213213
def _action(self, serializer):
@@ -231,7 +231,7 @@ class SetUsernameView(utils.ActionViewMixin, generics.GenericAPIView):
231231
"""
232232
Use this endpoint to change user username.
233233
"""
234-
permission_classes = [permissions.IsAuthenticated]
234+
permission_classes = settings.PERMISSIONS.set_username
235235

236236
def get_serializer_class(self):
237237
if settings.SET_USERNAME_RETYPE:
@@ -259,7 +259,7 @@ class UserView(generics.RetrieveUpdateAPIView):
259259
"""
260260
queryset = User.objects.all()
261261
serializer_class = settings.SERIALIZERS.user
262-
permission_classes = [permissions.IsAuthenticated]
262+
permission_classes = settings.PERMISSIONS.user
263263

264264
def get_object(self, *args, **kwargs):
265265
return self.request.user
@@ -276,14 +276,16 @@ def perform_update(self, serializer):
276276
class UserViewSet(UserCreateView, viewsets.ModelViewSet):
277277
serializer_class = settings.SERIALIZERS.user
278278
queryset = User.objects.all()
279-
permission_classes = [CurrentUserOrAdmin]
279+
permission_classes = settings.PERMISSIONS.user
280280
token_generator = default_token_generator
281281

282282
def get_permissions(self):
283-
if self.action in ['create', 'confirm']:
284-
self.permission_classes = [permissions.AllowAny]
283+
if self.action == 'create':
284+
self.permission_classes = settings.PERMISSIONS.user_create
285+
elif self.action == 'confirm':
286+
self.permission_classes = settings.PERMISSIONS.activation
285287
elif self.action == 'list':
286-
self.permission_classes = [permissions.IsAdminUser]
288+
self.permission_classes = settings.PERMISSIONS.user_list
287289
return super(UserViewSet, self).get_permissions()
288290

289291
def get_serializer_class(self):

docs/source/settings.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,36 @@ List of allowed redirect URIs for social authentication.
195195

196196
**Example**: ``['https://auth.example.com']``
197197
**Default**: ``[]``
198+
199+
.. _view-permission-settings
200+
201+
PERMISSIONS
202+
-----------
203+
204+
Dictionary that maps permissions to certain views across Djoser.
205+
206+
207+
**Examples**
208+
209+
.. code-block:: python
210+
211+
{
212+
'user': ['djoser.permissions.CurrentUserOrAdminOrReadOnly']
213+
}
214+
215+
**Defaults**
216+
.. code-block:: python
217+
218+
{
219+
'activation': ['rest_framework.permissions.AllowAny'],
220+
'password_reset': ['rest_framework.permissions.AllowAny'],
221+
'password_reset_confirm': ['rest_framework.permissions.AllowAny'],
222+
'set_password': ['djoser.permissions.CurrentUserOrAdmin'],
223+
'set_username': ['rest_framework.permissions.IsAuthenticated'],
224+
'user_create': ['rest_framework.permissions.AllowAny'],
225+
'user_delete': ['djoser.permissions.CurrentUserOrAdmin'],
226+
'user': ['djoser.permissions.CurrentUserOrAdminOrReadOnly'],
227+
'user_list': ['djoser.permissions.CurrentUserOrAdminOrReadOnly'],
228+
'token_create': ['rest_framework.permissions.AllowAny'],
229+
'token_destroy': ['rest_framework.permissions.IsAuthenticated'],
230+
}

testproject/testapp/tests/test_user_create.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def test_patch_edits_user_attribute(self):
240240
user = create_user()
241241
self.client.force_authenticate(user=user)
242242
response = self.client.patch(
243-
path=reverse('user-detail', args=(user.pk, )),
243+
path=reverse('user-detail', args=(user.pk,)),
244244
data={'email': '[email protected]'}
245245
)
246246

@@ -259,12 +259,11 @@ def test_patch_cant_edit_others_attribute(self):
259259
})
260260
self.client.force_authenticate(user=user)
261261
response = self.client.patch(
262-
path=reverse('user-detail', args=(another_user.pk, )),
262+
path=reverse('user-detail', args=(another_user.pk,)),
263263
data={'email': '[email protected]'}
264264
)
265265

266266
self.assert_status_equal(response, status.HTTP_403_FORBIDDEN)
267267

268268
another_user.refresh_from_db()
269269
self.assertTrue(another_user.email == '[email protected]')
270-

0 commit comments

Comments
 (0)