Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
project
drupal
Commits
f969d2c1
Commit
f969d2c1
authored
Mar 27, 2013
by
Dries Buytaert
Browse files
Issue
#1866908
by klausi: Honor entity and field access control in REST Services.
parent
5818a99b
Changes
8
Hide whitespace changes
Inline
Side-by-side
core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityWrapper.php
View file @
f969d2c1
...
...
@@ -111,6 +111,7 @@ public function getTypeUri() {
public
function
getProperties
()
{
// Properties to skip.
$skip
=
array
(
'id'
);
$properties
=
array
();
// Create language map property structure.
foreach
(
$this
->
entity
->
getTranslationLanguages
()
as
$langcode
=>
$language
)
{
...
...
core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
View file @
f969d2c1
...
...
@@ -13,6 +13,7 @@
use
Drupal\Core\Entity\EntityStorageException
;
use
Drupal\rest\Plugin\ResourceBase
;
use
Drupal\rest\ResourceResponse
;
use
Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
;
use
Symfony\Component\HttpKernel\Exception\BadRequestHttpException
;
use
Symfony\Component\HttpKernel\Exception\HttpException
;
use
Symfony\Component\HttpKernel\Exception\NotFoundHttpException
;
...
...
@@ -44,6 +45,14 @@ public function get($id) {
$definition
=
$this
->
getDefinition
();
$entity
=
entity_load
(
$definition
[
'entity_type'
],
$id
);
if
(
$entity
)
{
if
(
!
$entity
->
access
(
'view'
))
{
throw
new
AccessDeniedHttpException
();
}
foreach
(
$entity
as
$field_name
=>
$field
)
{
if
(
!
$field
->
access
(
'view'
))
{
unset
(
$entity
->
{
$field_name
});
}
}
return
new
ResourceResponse
(
$entity
);
}
throw
new
NotFoundHttpException
(
t
(
'Entity with ID @id not found'
,
array
(
'@id'
=>
$id
)));
...
...
@@ -63,6 +72,9 @@ public function get($id) {
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public
function
post
(
$id
,
EntityInterface
$entity
)
{
if
(
!
$entity
->
access
(
'create'
))
{
throw
new
AccessDeniedHttpException
();
}
$definition
=
$this
->
getDefinition
();
// Verify that the deserialized entity is of the type that we expect to
// prevent security issues.
...
...
@@ -74,6 +86,11 @@ public function post($id, EntityInterface $entity) {
if
(
!
$entity
->
isNew
())
{
throw
new
BadRequestHttpException
(
t
(
'Only new entities can be created'
));
}
foreach
(
$entity
as
$field_name
=>
$field
)
{
if
(
!
$field
->
access
(
'create'
))
{
throw
new
AccessDeniedHttpException
(
t
(
'Access denied on creating field @field.'
,
array
(
'@field'
=>
$field_name
)));
}
}
try
{
$entity
->
save
();
watchdog
(
'rest'
,
'Created entity %type with ID %id.'
,
array
(
'%type'
=>
$entity
->
entityType
(),
'%id'
=>
$entity
->
id
()));
...
...
@@ -114,10 +131,27 @@ public function patch($id, EntityInterface $entity) {
if
(
$original_entity
==
FALSE
)
{
throw
new
NotFoundHttpException
();
}
if
(
!
$original_entity
->
access
(
'update'
))
{
throw
new
AccessDeniedHttpException
();
}
$info
=
$original_entity
->
entityInfo
();
// Make sure that the entity ID is the one provided in the URL.
$entity
->
{
$info
[
'entity_keys'
][
'id'
]}
=
$id
;
// Overwrite the received properties.
foreach
(
$entity
->
getProperties
()
as
$name
=>
$property
)
{
if
(
isset
(
$entity
->
{
$name
}))
{
$original_entity
->
{
$name
}
=
$property
;
foreach
(
$entity
as
$field_name
=>
$field
)
{
if
(
isset
(
$entity
->
{
$field_name
}))
{
if
(
empty
(
$entity
->
{
$field_name
}))
{
if
(
!
$original_entity
->
{
$field_name
}
->
access
(
'delete'
))
{
throw
new
AccessDeniedHttpException
(
t
(
'Access denied on deleting field @field.'
,
array
(
'@field'
=>
$field_name
)));
}
}
else
{
if
(
!
$original_entity
->
{
$field_name
}
->
access
(
'update'
))
{
throw
new
AccessDeniedHttpException
(
t
(
'Access denied on updating field @field.'
,
array
(
'@field'
=>
$field_name
)));
}
}
$original_entity
->
{
$field_name
}
=
$field
;
}
}
try
{
...
...
@@ -147,6 +181,9 @@ public function delete($id) {
$definition
=
$this
->
getDefinition
();
$entity
=
entity_load
(
$definition
[
'entity_type'
],
$id
);
if
(
$entity
)
{
if
(
!
$entity
->
access
(
'delete'
))
{
throw
new
AccessDeniedHttpException
();
}
try
{
$entity
->
delete
();
watchdog
(
'rest'
,
'Deleted entity %type with ID %id.'
,
array
(
'%type'
=>
$entity
->
entityType
(),
'%id'
=>
$entity
->
id
()));
...
...
@@ -160,18 +197,4 @@ public function delete($id) {
}
throw
new
NotFoundHttpException
(
t
(
'Entity with ID @id not found'
,
array
(
'@id'
=>
$id
)));
}
/**
* Overrides ResourceBase::permissions().
*/
public
function
permissions
()
{
$permissions
=
parent
::
permissions
();
// Mark all items as administrative permissions for now.
// @todo Remove this restriction once proper entity access control is
// implemented. See http://drupal.org/node/1866908
foreach
(
$permissions
as
$name
=>
$permission
)
{
$permissions
[
$name
][
'restrict access'
]
=
TRUE
;
}
return
$permissions
;
}
}
core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
View file @
f969d2c1
...
...
@@ -41,7 +41,9 @@ public function testCreate() {
$this
->
enableService
(
'entity:'
.
$entity_type
,
'POST'
);
// Create a user account that has the required permissions to create
// resources via the REST API.
$account
=
$this
->
drupalCreateUser
(
array
(
'restful post entity:'
.
$entity_type
));
$permissions
=
$this
->
entityPermissions
(
$entity_type
,
'create'
);
$permissions
[]
=
'restful post entity:'
.
$entity_type
;
$account
=
$this
->
drupalCreateUser
(
$permissions
);
$this
->
drupalLogin
(
$account
);
$entity_values
=
$this
->
entityValues
(
$entity_type
);
...
...
@@ -70,6 +72,18 @@ public function testCreate() {
$loaded_entity
->
delete
();
// Try to create an entity with an access protected field.
// @see entity_test_entity_field_access()
$entity
->
field_test_text
->
value
=
'no access value'
;
$serialized
=
$serializer
->
serialize
(
$entity
,
'drupal_jsonld'
);
$this
->
httpRequest
(
'entity/'
.
$entity_type
,
'POST'
,
$serialized
,
'application/vnd.drupal.ld+json'
);
$this
->
assertResponse
(
403
);
$this
->
assertFalse
(
entity_load_multiple
(
$entity_type
,
NULL
,
TRUE
),
'No entity has been created in the database.'
);
// Restore the valid test value.
$entity
->
field_test_text
->
value
=
$entity_values
[
'field_test_text'
][
0
][
'value'
];
$serialized
=
$serializer
->
serialize
(
$entity
,
'drupal_jsonld'
);
// Try to send invalid data that cannot be correctly deserialized.
$this
->
httpRequest
(
'entity/'
.
$entity_type
,
'POST'
,
'kaboom!'
,
'application/vnd.drupal.ld+json'
);
$this
->
assertResponse
(
400
);
...
...
core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
View file @
f969d2c1
...
...
@@ -34,12 +34,16 @@ public static function getInfo() {
*/
public
function
testDelete
()
{
// Define the entity types we want to test.
$entity_types
=
array
(
'entity_test'
,
'node'
,
'user'
);
// @todo expand this test to at least nodes and users once their access
// controllers are implemented.
$entity_types
=
array
(
'entity_test'
);
foreach
(
$entity_types
as
$entity_type
)
{
$this
->
enableService
(
'entity:'
.
$entity_type
,
'DELETE'
);
// Create a user account that has the required permissions to delete
// resources via the REST API.
$account
=
$this
->
drupalCreateUser
(
array
(
'restful delete entity:'
.
$entity_type
));
$permissions
=
$this
->
entityPermissions
(
$entity_type
,
'delete'
);
$permissions
[]
=
'restful delete entity:'
.
$entity_type
;
$account
=
$this
->
drupalCreateUser
(
$permissions
);
$this
->
drupalLogin
(
$account
);
// Create an entity programmatically.
...
...
core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
View file @
f969d2c1
...
...
@@ -218,4 +218,29 @@ protected function drupalLogin($user) {
}
parent
::
drupalLogin
(
$user
);
}
/**
* Provides the necessary user permissions for entity operations.
*
* @param string $entity_type
* The entity type.
* @param type $operation
* The operation, one of 'view', 'create', 'update' or 'delete'.
*
* @return array
* The set of user permission strings.
*/
protected
function
entityPermissions
(
$entity_type
,
$operation
)
{
switch
(
$entity_type
)
{
case
'entity_test'
:
switch
(
$operation
)
{
case
'view'
:
return
array
(
'view test entity'
);
case
'create'
:
case
'update'
:
case
'delete'
:
return
array
(
'administer entity_test content'
);
}
}
}
}
core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
View file @
f969d2c1
...
...
@@ -39,9 +39,11 @@ public function testRead() {
$entity_types
=
array
(
'entity_test'
);
foreach
(
$entity_types
as
$entity_type
)
{
$this
->
enableService
(
'entity:'
.
$entity_type
,
'GET'
);
// Create a user account that has the required permissions to
delete
// Create a user account that has the required permissions to
read
// resources via the REST API.
$account
=
$this
->
drupalCreateUser
(
array
(
'restful get entity:'
.
$entity_type
));
$permissions
=
$this
->
entityPermissions
(
$entity_type
,
'view'
);
$permissions
[]
=
'restful get entity:'
.
$entity_type
;
$account
=
$this
->
drupalCreateUser
(
$permissions
);
$this
->
drupalLogin
(
$account
);
// Create an entity programmatically.
...
...
@@ -66,6 +68,17 @@ public function testRead() {
$decoded
=
drupal_json_decode
(
$response
);
$this
->
assertEqual
(
$decoded
[
'error'
],
'Entity with ID 9999 not found'
,
'Response message is correct.'
);
// Make sure that field level access works and that the according field is
// not available in the response.
// @see entity_test_entity_field_access()
$entity
->
field_test_text
->
value
=
'no access value'
;
$entity
->
save
();
$response
=
$this
->
httpRequest
(
'entity/'
.
$entity_type
.
'/'
.
$entity
->
id
(),
'GET'
,
NULL
,
'application/vnd.drupal.ld+json'
);
$this
->
assertResponse
(
200
);
$this
->
assertHeader
(
'content-type'
,
'application/vnd.drupal.ld+json'
);
$data
=
drupal_json_decode
(
$response
);
$this
->
assertFalse
(
isset
(
$data
[
'field_test_text'
]),
'Field access protexted field is not visible in the response.'
);
// Try to read an entity without proper permissions.
$this
->
drupalLogout
();
$response
=
$this
->
httpRequest
(
'entity/'
.
$entity_type
.
'/'
.
$entity
->
id
(),
'GET'
,
NULL
,
'application/vnd.drupal.ld+json'
);
...
...
core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php
View file @
f969d2c1
...
...
@@ -41,7 +41,9 @@ public function testPatchUpdate() {
$this
->
enableService
(
'entity:'
.
$entity_type
,
'PATCH'
);
// Create a user account that has the required permissions to create
// resources via the REST API.
$account
=
$this
->
drupalCreateUser
(
array
(
'restful patch entity:'
.
$entity_type
));
$permissions
=
$this
->
entityPermissions
(
$entity_type
,
'update'
);
$permissions
[]
=
'restful patch entity:'
.
$entity_type
;
$account
=
$this
->
drupalCreateUser
(
$permissions
);
$this
->
drupalLogin
(
$account
);
// Create an entity and save it to the database.
...
...
@@ -76,6 +78,32 @@ public function testPatchUpdate() {
$entity
=
entity_load
(
$entity_type
,
$entity
->
id
(),
TRUE
);
$this
->
assertNull
(
$entity
->
field_test_text
->
value
,
'Test field has been cleared.'
);
// Enable access protection for the text field.
// @see entity_test_entity_field_access()
$entity
->
field_test_text
->
value
=
'no access value'
;
$entity
->
save
();
// Try to empty a field that is access protected.
$this
->
httpRequest
(
'entity/'
.
$entity_type
.
'/'
.
$entity
->
id
(),
'PATCH'
,
$serialized
,
'application/vnd.drupal.ld+json'
);
$this
->
assertResponse
(
403
);
// Re-load the entity from the database.
$entity
=
entity_load
(
$entity_type
,
$entity
->
id
(),
TRUE
);
$this
->
assertEqual
(
$entity
->
field_test_text
->
value
,
'no access value'
,
'Text field was not updated.'
);
// Try to update an access protected field.
$serialized
=
$serializer
->
serialize
(
$patch_entity
,
'drupal_jsonld'
);
$this
->
httpRequest
(
'entity/'
.
$entity_type
.
'/'
.
$entity
->
id
(),
'PATCH'
,
$serialized
,
'application/vnd.drupal.ld+json'
);
$this
->
assertResponse
(
403
);
// Re-load the entity from the database.
$entity
=
entity_load
(
$entity_type
,
$entity
->
id
(),
TRUE
);
$this
->
assertEqual
(
$entity
->
field_test_text
->
value
,
'no access value'
,
'Text field was not updated.'
);
// Restore the valid test value.
$entity
->
field_test_text
->
value
=
$this
->
randomString
();
$entity
->
save
();
// Try to update a non-existing entity with ID 9999.
$this
->
httpRequest
(
'entity/'
.
$entity_type
.
'/9999'
,
'PATCH'
,
$serialized
,
'application/vnd.drupal.ld+json'
);
$this
->
assertResponse
(
404
);
...
...
core/modules/system/tests/modules/entity_test/entity_test.module
View file @
f969d2c1
...
...
@@ -289,8 +289,13 @@ function entity_test_entity_view_mode_info() {
* @see \Drupal\system\Tests\Entity\FieldAccessTest::testFieldAccess()
*/
function
entity_test_entity_field_access
(
$operation
,
$field
,
$account
)
{
if
(
$field
->
getName
()
==
'field_test_text'
&&
$field
->
value
==
'no access value'
)
{
return
FALSE
;
if
(
$field
->
getName
()
==
'field_test_text'
)
{
if
(
$field
->
value
==
'no access value'
)
{
return
FALSE
;
}
elseif
(
$operation
==
'delete'
&&
$field
->
value
==
'no delete access value'
)
{
return
FALSE
;
}
}
}
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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