Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
django-rest-framework
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
django-rest-framework
Commits
75fb4b02
Commit
75fb4b02
authored
Sep 10, 2013
by
Tom Christie
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
git://github.com/bwreilly/django-rest-framework
into bwreilly-master
parents
f5c34926
23fc9dd5
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
211 additions
and
30 deletions
+211
-30
docs/index.md
+1
-0
rest_framework/compat.py
+6
-0
rest_framework/filters.py
+17
-1
rest_framework/permissions.py
+45
-0
rest_framework/runtests/settings.py
+15
-0
rest_framework/tests/test_permissions.py
+119
-29
tox.ini
+8
-0
No files found.
docs/index.md
View file @
75fb4b02
...
...
@@ -42,6 +42,7 @@ The following packages are optional:
*
[
django-filter
][
django-filter
]
(0.5.4+) - Filtering support.
*
[
django-oauth-plus
][
django-oauth-plus
]
(2.0+) and
[
oauth2
][
oauth2
]
(1.5.211+) - OAuth 1.0a support.
*
[
django-oauth2-provider
][
django-oauth2-provider
]
(0.2.3+) - OAuth 2.0 support.
*
[
django-guardian
][
django-guardian
]
(1.1.1+) - Object level permissions support.
**Note**
: The
`oauth2`
Python package is badly misnamed, and actually provides OAuth 1.0a support. Also note that packages required for both OAuth 1.0a, and OAuth 2.0 are not yet Python 3 compatible.
...
...
rest_framework/compat.py
View file @
75fb4b02
...
...
@@ -47,6 +47,12 @@ try:
except
ImportError
:
django_filters
=
None
# guardian is optional
try
:
import
guardian
except
ImportError
:
guardian
=
None
# cStringIO only if it's available, otherwise StringIO
try
:
...
...
rest_framework/filters.py
View file @
75fb4b02
...
...
@@ -4,7 +4,7 @@ returned by list views.
"""
from
__future__
import
unicode_literals
from
django.db
import
models
from
rest_framework.compat
import
django_filters
,
six
from
rest_framework.compat
import
django_filters
,
six
,
guardian
from
functools
import
reduce
import
operator
...
...
@@ -23,6 +23,22 @@ class BaseFilterBackend(object):
raise
NotImplementedError
(
".filter_queryset() must be overridden."
)
class
ObjectPermissionReaderFilter
(
BaseFilterBackend
):
"""
A filter backend that limits results to those where the requesting user
has read object level permissions.
"""
def
__init__
(
self
):
assert
guardian
,
'Using ObjectPermissionReaderFilter, but django-guardian is not installed'
def
filter_queryset
(
self
,
request
,
queryset
,
view
):
user
=
request
.
user
model_cls
=
queryset
.
model
model_name
=
model_cls
.
_meta
.
module_name
permission
=
'read_'
+
model_name
return
guardian
.
shortcuts
.
get_objects_for_user
(
user
,
permission
,
queryset
)
class
DjangoFilterBackend
(
BaseFilterBackend
):
"""
A filter backend that uses django-filter.
...
...
rest_framework/permissions.py
View file @
75fb4b02
...
...
@@ -7,6 +7,7 @@ import warnings
SAFE_METHODS
=
[
'GET'
,
'HEAD'
,
'OPTIONS'
]
from
django.http
import
Http404
from
rest_framework.compat
import
oauth2_provider_scope
,
oauth2_constants
...
...
@@ -151,6 +152,50 @@ class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
authenticated_users_only
=
False
class
DjangoObjectLevelModelPermissions
(
DjangoModelPermissions
):
"""
The request is authenticated using `django.contrib.auth` permissions.
See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions
It ensures that the user is authenticated, and has the appropriate
`add`/`change`/`delete` permissions on the object using .has_perms.
This permission can only be applied against view classes that
provide a `.model` or `.queryset` attribute.
"""
actions_map
=
{
'GET'
:
[
'read_
%(model_name)
s'
],
'OPTIONS'
:
[
'read_
%(model_name)
s'
],
'HEAD'
:
[
'read_
%(model_name)
s'
],
'POST'
:
[
'add_
%(model_name)
s'
],
'PUT'
:
[
'change_
%(model_name)
s'
],
'PATCH'
:
[
'change_
%(model_name)
s'
],
'DELETE'
:
[
'delete_
%(model_name)
s'
],
}
def
get_required_object_permissions
(
self
,
method
,
model_cls
):
kwargs
=
{
'model_name'
:
model_cls
.
_meta
.
module_name
}
return
[
perm
%
kwargs
for
perm
in
self
.
actions_map
[
method
]]
def
has_object_permission
(
self
,
request
,
view
,
obj
):
model_cls
=
getattr
(
view
,
'model'
,
None
)
queryset
=
getattr
(
view
,
'queryset'
,
None
)
if
model_cls
is
None
and
queryset
is
not
None
:
model_cls
=
queryset
.
model
perms
=
self
.
get_required_object_permissions
(
request
.
method
,
model_cls
)
user
=
request
.
user
check
=
user
.
has_perms
(
perms
,
obj
)
if
not
check
:
raise
Http404
return
user
.
has_perms
(
perms
,
obj
)
class
TokenHasReadWriteScope
(
BasePermission
):
"""
The request is authenticated as a user and the token used has the right scope
...
...
rest_framework/runtests/settings.py
View file @
75fb4b02
...
...
@@ -123,6 +123,21 @@ else:
'provider.oauth2'
,
)
# guardian is optional
try
:
import
guardian
except
ImportError
:
pass
else
:
ANONYMOUS_USER_ID
=
-
1
AUTHENTICATION_BACKENDS
=
(
'django.contrib.auth.backends.ModelBackend'
,
# default
'guardian.backends.ObjectPermissionBackend'
,
)
INSTALLED_APPS
+=
(
'guardian'
,
)
STATIC_URL
=
'/static/'
PASSWORD_HASHERS
=
(
...
...
rest_framework/tests/test_permissions.py
View file @
75fb4b02
from
__future__
import
unicode_literals
from
django.contrib.auth.models
import
User
,
Permission
from
django.contrib.auth.models
import
User
,
Permission
,
Group
from
django.db
import
models
from
django.test
import
TestCase
from
rest_framework
import
generics
,
status
,
permissions
,
authentication
,
HTTP_HEADER_ENCODING
from
rest_framework.compat
import
guardian
from
rest_framework.filters
import
ObjectPermissionReaderFilter
from
rest_framework.test
import
APIRequestFactory
from
rest_framework.tests.models
import
BasicModel
import
base64
factory
=
APIRequestFactory
()
class
BasicModel
(
models
.
Model
):
text
=
models
.
CharField
(
max_length
=
100
)
class
RootView
(
generics
.
ListCreateAPIView
):
model
=
BasicModel
authentication_classes
=
[
authentication
.
BasicAuthentication
]
...
...
@@ -144,45 +142,136 @@ class ModelPermissionsIntegrationTests(TestCase):
self
.
assertEqual
(
list
(
response
.
data
[
'actions'
]
.
keys
()),
[
'PUT'
])
class
Owner
Model
(
models
.
Model
):
class
BasicPerm
Model
(
models
.
Model
):
text
=
models
.
CharField
(
max_length
=
100
)
owner
=
models
.
ForeignKey
(
User
)
class
Meta
:
app_label
=
'tests'
permissions
=
(
(
'read_basicpermmodel'
,
'Can view basic perm model'
),
# add, change, delete built in to django
)
class
IsOwnerPermission
(
permissions
.
BasePermission
):
def
has_object_permission
(
self
,
request
,
view
,
obj
):
return
request
.
user
==
obj
.
owner
class
ObjectPermissionInstanceView
(
generics
.
RetrieveUpdateDestroyAPIView
):
model
=
BasicPermModel
authentication_classes
=
[
authentication
.
BasicAuthentication
]
permission_classes
=
[
permissions
.
DjangoObjectLevelModelPermissions
]
object_permissions_view
=
ObjectPermissionInstanceView
.
as_view
()
class
O
wnerInstanceView
(
generics
.
RetrieveUpdateDestroy
APIView
):
model
=
Owner
Model
class
O
bjectPermissionListView
(
generics
.
List
APIView
):
model
=
BasicPerm
Model
authentication_classes
=
[
authentication
.
BasicAuthentication
]
permission_classes
=
[
IsOwnerPermission
]
permission_classes
=
[
permissions
.
DjangoObjectLevelModelPermissions
]
object_permissions_list_view
=
ObjectPermissionListView
.
as_view
()
owner_instance_view
=
OwnerInstanceView
.
as_view
()
if
guardian
:
from
guardian.shortcuts
import
assign_perm
class
ObjectPermissionsIntegrationTests
(
TestCase
):
class
ObjectPermissionsIntegrationTests
(
TestCase
):
"""
Integration tests for the object level permissions API.
"""
@classmethod
def
setUpClass
(
cls
):
# create users
create
=
User
.
objects
.
create_user
users
=
{
'fullaccess'
:
create
(
'fullaccess'
,
'fullaccess@example.com'
,
'password'
),
'readonly'
:
create
(
'readonly'
,
'readonly@example.com'
,
'password'
),
'writeonly'
:
create
(
'writeonly'
,
'writeonly@example.com'
,
'password'
),
'deleteonly'
:
create
(
'deleteonly'
,
'deleteonly@example.com'
,
'password'
),
}
# give everyone model level permissions, as we are not testing those
everyone
=
Group
.
objects
.
create
(
name
=
'everyone'
)
model_name
=
BasicPermModel
.
_meta
.
module_name
app_label
=
BasicPermModel
.
_meta
.
app_label
f
=
'{0}_{1}'
.
format
perms
=
{
'read'
:
f
(
'read'
,
model_name
),
'change'
:
f
(
'change'
,
model_name
),
'delete'
:
f
(
'delete'
,
model_name
)
}
for
perm
in
perms
.
values
():
perm
=
'{0}.{1}'
.
format
(
app_label
,
perm
)
assign_perm
(
perm
,
everyone
)
everyone
.
user_set
.
add
(
*
users
.
values
())
cls
.
perms
=
perms
cls
.
users
=
users
def
setUp
(
self
):
User
.
objects
.
create_user
(
'not_owner'
,
'not_owner@example.com'
,
'password'
)
user
=
User
.
objects
.
create_user
(
'owner'
,
'owner@example.com'
,
'password'
)
perms
=
self
.
perms
users
=
self
.
users
# appropriate object level permissions
readers
=
Group
.
objects
.
create
(
name
=
'readers'
)
writers
=
Group
.
objects
.
create
(
name
=
'writers'
)
deleters
=
Group
.
objects
.
create
(
name
=
'deleters'
)
model
=
BasicPermModel
.
objects
.
create
(
text
=
'foo'
)
assign_perm
(
perms
[
'read'
],
readers
,
model
)
assign_perm
(
perms
[
'change'
],
writers
,
model
)
assign_perm
(
perms
[
'delete'
],
deleters
,
model
)
self
.
not_owner_credentials
=
basic_auth_header
(
'not_owner'
,
'password'
)
self
.
owner_credentials
=
basic_auth_header
(
'owner'
,
'password'
)
readers
.
user_set
.
add
(
users
[
'fullaccess'
],
users
[
'readonly'
])
writers
.
user_set
.
add
(
users
[
'fullaccess'
],
users
[
'writeonly'
])
deleters
.
user_set
.
add
(
users
[
'fullaccess'
],
users
[
'deleteonly'
])
OwnerModel
(
text
=
'foo'
,
owner
=
user
)
.
save
()
self
.
credentials
=
{}
for
user
in
users
.
values
():
self
.
credentials
[
user
.
username
]
=
basic_auth_header
(
user
.
username
,
'password'
)
def
test_owner_has_delete_permissions
(
self
):
request
=
factory
.
delete
(
'/1'
,
HTTP_AUTHORIZATION
=
self
.
owner_credentials
)
response
=
owner_instance_view
(
request
,
pk
=
'1'
)
# Delete
def
test_can_delete_permissions
(
self
):
request
=
factory
.
delete
(
'/1'
,
HTTP_AUTHORIZATION
=
self
.
credentials
[
'deleteonly'
])
response
=
object_permissions_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_204_NO_CONTENT
)
def
test_non_owner_does_not_have_delete_permissions
(
self
):
request
=
factory
.
delete
(
'/1'
,
HTTP_AUTHORIZATION
=
self
.
not_owner_credentials
)
response
=
owner_instance_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_403_FORBIDDEN
)
def
test_cannot_delete_permissions
(
self
):
request
=
factory
.
delete
(
'/1'
,
HTTP_AUTHORIZATION
=
self
.
credentials
[
'readonly'
])
response
=
object_permissions_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_404_NOT_FOUND
)
# Update
def
test_can_update_permissions
(
self
):
request
=
factory
.
patch
(
'/1'
,
{
'text'
:
'foobar'
},
format
=
'json'
,
HTTP_AUTHORIZATION
=
self
.
credentials
[
'writeonly'
])
response
=
object_permissions_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
data
.
get
(
'text'
),
'foobar'
)
def
test_cannot_update_permissions
(
self
):
request
=
factory
.
patch
(
'/1'
,
{
'text'
:
'foobar'
},
format
=
'json'
,
HTTP_AUTHORIZATION
=
self
.
credentials
[
'deleteonly'
])
response
=
object_permissions_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_404_NOT_FOUND
)
# Read
def
test_can_read_permissions
(
self
):
request
=
factory
.
get
(
'/1'
,
HTTP_AUTHORIZATION
=
self
.
credentials
[
'readonly'
])
response
=
object_permissions_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
def
test_cannot_read_permissions
(
self
):
request
=
factory
.
get
(
'/1'
,
HTTP_AUTHORIZATION
=
self
.
credentials
[
'writeonly'
])
response
=
object_permissions_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_404_NOT_FOUND
)
# Read list
def
test_can_read_list_permissions
(
self
):
request
=
factory
.
get
(
'/'
,
HTTP_AUTHORIZATION
=
self
.
credentials
[
'readonly'
])
object_permissions_list_view
.
cls
.
filter_backends
=
(
ObjectPermissionReaderFilter
,)
response
=
object_permissions_list_view
(
request
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
data
[
0
]
.
get
(
'id'
),
1
)
def
test_cannot_read_list_permissions
(
self
):
request
=
factory
.
get
(
'/'
,
HTTP_AUTHORIZATION
=
self
.
credentials
[
'writeonly'
])
object_permissions_list_view
.
cls
.
filter_backends
=
(
ObjectPermissionReaderFilter
,)
response
=
object_permissions_list_view
(
request
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertListEqual
(
response
.
data
,
[])
\ No newline at end of file
tox.ini
View file @
75fb4b02
...
...
@@ -25,6 +25,7 @@ deps = https://www.djangoproject.com/download/1.6a1/tarball/
django-oauth-plus
=
=2.0
oauth2
=
=1.5.211
django-oauth2-provider
=
=0.2.4
django-guardian
=
=1.1.1
[testenv:py2.6-django1.6]
basepython
=
python2.6
...
...
@@ -34,6 +35,7 @@ deps = https://www.djangoproject.com/download/1.6a1/tarball/
django-oauth-plus
=
=2.0
oauth2
=
=1.5.211
django-oauth2-provider
=
=0.2.4
django-guardian
=
=1.1.1
[testenv:py3.3-django1.5]
basepython
=
python3.3
...
...
@@ -55,6 +57,7 @@ deps = django==1.5
django-oauth-plus
=
=2.0
oauth2
=
=1.5.211
django-oauth2-provider
=
=0.2.3
django-guardian
=
=1.1.1
[testenv:py2.6-django1.5]
basepython
=
python2.6
...
...
@@ -64,6 +67,7 @@ deps = django==1.5
django-oauth-plus
=
=2.0
oauth2
=
=1.5.211
django-oauth2-provider
=
=0.2.3
django-guardian
=
=1.1.1
[testenv:py2.7-django1.4]
basepython
=
python2.7
...
...
@@ -73,6 +77,7 @@ deps = django==1.4.3
django-oauth-plus
=
=2.0
oauth2
=
=1.5.211
django-oauth2-provider
=
=0.2.3
django-guardian
=
=1.1.1
[testenv:py2.6-django1.4]
basepython
=
python2.6
...
...
@@ -82,6 +87,7 @@ deps = django==1.4.3
django-oauth-plus
=
=2.0
oauth2
=
=1.5.211
django-oauth2-provider
=
=0.2.3
django-guardian
=
=1.1.1
[testenv:py2.7-django1.3]
basepython
=
python2.7
...
...
@@ -91,6 +97,7 @@ deps = django==1.3.5
django-oauth-plus
=
=2.0
oauth2
=
=1.5.211
django-oauth2-provider
=
=0.2.3
django-guardian
=
=1.1.1
[testenv:py2.6-django1.3]
basepython
=
python2.6
...
...
@@ -100,3 +107,4 @@ deps = django==1.3.5
django-oauth-plus
=
=2.0
oauth2
=
=1.5.211
django-oauth2-provider
=
=0.2.3
django-guardian
=
=1.1.1
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment