mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
BUGFIX Respecting api_access on has_one relations in RestfulServer
BUGFIX Limiting fields according to api_access on relation object (rather than the "root" object) in RestfulServer BUGFIX Limit listing of has_one relations in RestfulServer to actual relation (was listing all objects before) BUGFIX Creating correct object instances in RestfulServer->getHandler() for relation queries
This commit is contained in:
parent
ec47cc1286
commit
d30b4b1d00
@ -214,7 +214,7 @@ class RestfulServer extends Controller {
|
|||||||
|
|
||||||
$params = $this->request->getVars();
|
$params = $this->request->getVars();
|
||||||
|
|
||||||
$responseFormatter = $this->getResponseDataFormatter();
|
$responseFormatter = $this->getResponseDataFormatter($className);
|
||||||
if(!$responseFormatter) return $this->unsupportedMediaType();
|
if(!$responseFormatter) return $this->unsupportedMediaType();
|
||||||
|
|
||||||
// $obj can be either a DataObject or a SS_List,
|
// $obj can be either a DataObject or a SS_List,
|
||||||
@ -229,6 +229,9 @@ class RestfulServer extends Controller {
|
|||||||
if($relationName) {
|
if($relationName) {
|
||||||
$obj = $this->getObjectRelationQuery($obj, $params, $sort, $limit, $relationName);
|
$obj = $this->getObjectRelationQuery($obj, $params, $sort, $limit, $relationName);
|
||||||
if(!$obj) return $this->notFound();
|
if(!$obj) return $this->notFound();
|
||||||
|
|
||||||
|
// TODO Avoid creating data formatter again for relation class (see above)
|
||||||
|
$responseFormatter = $this->getResponseDataFormatter($obj->dataClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -270,7 +273,6 @@ class RestfulServer extends Controller {
|
|||||||
} else {
|
} else {
|
||||||
$searchContext = singleton($className)->getDefaultSearchContext();
|
$searchContext = singleton($className)->getDefaultSearchContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $searchContext->getQuery($params, $sort, $limit, $existingQuery);
|
return $searchContext->getQuery($params, $sort, $limit, $existingQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,15 +281,17 @@ class RestfulServer extends Controller {
|
|||||||
* extension or mimetype. Falls back to {@link self::$default_extension}.
|
* extension or mimetype. Falls back to {@link self::$default_extension}.
|
||||||
*
|
*
|
||||||
* @param boolean $includeAcceptHeader Determines wether to inspect and prioritize any HTTP Accept headers
|
* @param boolean $includeAcceptHeader Determines wether to inspect and prioritize any HTTP Accept headers
|
||||||
|
* @param String Classname of a DataObject
|
||||||
* @return DataFormatter
|
* @return DataFormatter
|
||||||
*/
|
*/
|
||||||
protected function getDataFormatter($includeAcceptHeader = false) {
|
protected function getDataFormatter($includeAcceptHeader = false, $className = null) {
|
||||||
$extension = $this->request->getExtension();
|
$extension = $this->request->getExtension();
|
||||||
$contentTypeWithEncoding = $this->request->getHeader('Content-Type');
|
$contentTypeWithEncoding = $this->request->getHeader('Content-Type');
|
||||||
preg_match('/([^;]*)/',$contentTypeWithEncoding, $contentTypeMatches);
|
preg_match('/([^;]*)/',$contentTypeWithEncoding, $contentTypeMatches);
|
||||||
$contentType = $contentTypeMatches[0];
|
$contentType = $contentTypeMatches[0];
|
||||||
$accept = $this->request->getHeader('Accept');
|
$accept = $this->request->getHeader('Accept');
|
||||||
$mimetypes = $this->request->getAcceptMimetypes();
|
$mimetypes = $this->request->getAcceptMimetypes();
|
||||||
|
if(!$className) $className = $this->urlParams['ClassName'];
|
||||||
|
|
||||||
// get formatter
|
// get formatter
|
||||||
if(!empty($extension)) {
|
if(!empty($extension)) {
|
||||||
@ -306,9 +310,9 @@ class RestfulServer extends Controller {
|
|||||||
// set custom fields
|
// set custom fields
|
||||||
if($customAddFields = $this->request->getVar('add_fields')) $formatter->setCustomAddFields(explode(',',$customAddFields));
|
if($customAddFields = $this->request->getVar('add_fields')) $formatter->setCustomAddFields(explode(',',$customAddFields));
|
||||||
if($customFields = $this->request->getVar('fields')) $formatter->setCustomFields(explode(',',$customFields));
|
if($customFields = $this->request->getVar('fields')) $formatter->setCustomFields(explode(',',$customFields));
|
||||||
$formatter->setCustomRelations($this->getAllowedRelations($this->urlParams['ClassName']));
|
$formatter->setCustomRelations($this->getAllowedRelations($className));
|
||||||
|
|
||||||
$apiAccess = singleton($this->urlParams['ClassName'])->stat('api_access');
|
$apiAccess = singleton($className)->stat('api_access');
|
||||||
if(is_array($apiAccess)) {
|
if(is_array($apiAccess)) {
|
||||||
$formatter->setCustomAddFields(array_intersect((array)$formatter->getCustomAddFields(), (array)$apiAccess['view']));
|
$formatter->setCustomAddFields(array_intersect((array)$formatter->getCustomAddFields(), (array)$apiAccess['view']));
|
||||||
if($formatter->getCustomFields()) {
|
if($formatter->getCustomFields()) {
|
||||||
@ -331,12 +335,20 @@ class RestfulServer extends Controller {
|
|||||||
return $formatter;
|
return $formatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getRequestDataFormatter() {
|
/**
|
||||||
return $this->getDataFormatter(false);
|
* @param String Classname of a DataObject
|
||||||
|
* @return DataFormatter
|
||||||
|
*/
|
||||||
|
protected function getRequestDataFormatter($className = null) {
|
||||||
|
return $this->getDataFormatter(false, $className);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getResponseDataFormatter() {
|
/**
|
||||||
return $this->getDataFormatter(true);
|
* @param String Classname of a DataObject
|
||||||
|
* @return DataFormatter
|
||||||
|
*/
|
||||||
|
protected function getResponseDataFormatter($className = null) {
|
||||||
|
return $this->getDataFormatter(true, $className);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -361,10 +373,10 @@ class RestfulServer extends Controller {
|
|||||||
if(!$obj) return $this->notFound();
|
if(!$obj) return $this->notFound();
|
||||||
if(!$obj->canEdit()) return $this->permissionFailure();
|
if(!$obj->canEdit()) return $this->permissionFailure();
|
||||||
|
|
||||||
$reqFormatter = $this->getRequestDataFormatter();
|
$reqFormatter = $this->getRequestDataFormatter($className);
|
||||||
if(!$reqFormatter) return $this->unsupportedMediaType();
|
if(!$reqFormatter) return $this->unsupportedMediaType();
|
||||||
|
|
||||||
$responseFormatter = $this->getResponseDataFormatter();
|
$responseFormatter = $this->getResponseDataFormatter($className);
|
||||||
if(!$responseFormatter) return $this->unsupportedMediaType();
|
if(!$responseFormatter) return $this->unsupportedMediaType();
|
||||||
|
|
||||||
$obj = $this->updateDataObject($obj, $reqFormatter);
|
$obj = $this->updateDataObject($obj, $reqFormatter);
|
||||||
@ -419,10 +431,10 @@ class RestfulServer extends Controller {
|
|||||||
if(!singleton($className)->canCreate()) return $this->permissionFailure();
|
if(!singleton($className)->canCreate()) return $this->permissionFailure();
|
||||||
$obj = new $className();
|
$obj = new $className();
|
||||||
|
|
||||||
$reqFormatter = $this->getRequestDataFormatter();
|
$reqFormatter = $this->getRequestDataFormatter($className);
|
||||||
if(!$reqFormatter) return $this->unsupportedMediaType();
|
if(!$reqFormatter) return $this->unsupportedMediaType();
|
||||||
|
|
||||||
$responseFormatter = $this->getResponseDataFormatter();
|
$responseFormatter = $this->getResponseDataFormatter($className);
|
||||||
|
|
||||||
$obj = $this->updateDataObject($obj, $reqFormatter);
|
$obj = $this->updateDataObject($obj, $reqFormatter);
|
||||||
|
|
||||||
@ -520,8 +532,17 @@ class RestfulServer extends Controller {
|
|||||||
protected function getObjectRelationQuery($obj, $params, $sort, $limit, $relationName) {
|
protected function getObjectRelationQuery($obj, $params, $sort, $limit, $relationName) {
|
||||||
// The relation method will return a DataList, that getSearchQuery subsequently manipulates
|
// The relation method will return a DataList, that getSearchQuery subsequently manipulates
|
||||||
if($obj->hasMethod($relationName)) {
|
if($obj->hasMethod($relationName)) {
|
||||||
$query = $obj->$relationName();
|
if($relationClass = $obj->has_one($relationName)) {
|
||||||
return $this->getSearchQuery($query->dataClass(), $params, $sort, $limit, $query);
|
$joinField = $relationName . 'ID';
|
||||||
|
$list = DataList::create($relationClass)->byIDs(array($obj->$joinField));
|
||||||
|
} else {
|
||||||
|
$list = $obj->$relationName();
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiAccess = singleton($list->dataClass())->stat('api_access');
|
||||||
|
if(!$apiAccess) return false;
|
||||||
|
|
||||||
|
return $this->getSearchQuery($list->dataClass(), $params, $sort, $limit, $list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,6 +305,7 @@ class RestfulServerTest extends SapphireTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testApiAccessFieldRestrictions() {
|
public function testApiAccessFieldRestrictions() {
|
||||||
|
$author1 = $this->objFromFixture('RestfulServerTest_Author','author1');
|
||||||
$rating1 = $this->objFromFixture('RestfulServerTest_AuthorRating','rating1');
|
$rating1 = $this->objFromFixture('RestfulServerTest_AuthorRating','rating1');
|
||||||
|
|
||||||
$url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID;
|
$url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID;
|
||||||
@ -332,15 +333,40 @@ class RestfulServerTest extends SapphireTest {
|
|||||||
$this->assertNotContains('<SecretRelation>', $response->getBody(),
|
$this->assertNotContains('<SecretRelation>', $response->getBody(),
|
||||||
'"fields" URL parameter filters out disallowed relations from $api_access'
|
'"fields" URL parameter filters out disallowed relations from $api_access'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$url = "/api/v1/RestfulServerTest_Author/" . $author1->ID . '/Ratings';
|
||||||
|
$response = Director::test($url, null, null, 'GET');
|
||||||
|
$this->assertContains('<Rating>', $response->getBody(),
|
||||||
|
'Relation viewer shows fields allowed through $api_access'
|
||||||
|
);
|
||||||
|
$this->assertNotContains('<SecretField>', $response->getBody(),
|
||||||
|
'Relation viewer on has-many filters out disallowed fields from $api_access'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApiAccessRelationRestrictions() {
|
public function testApiAccessRelationRestrictionsInline() {
|
||||||
$author1 = $this->objFromFixture('RestfulServerTest_Author','author1');
|
$author1 = $this->objFromFixture('RestfulServerTest_Author','author1');
|
||||||
|
|
||||||
$url = "/api/v1/RestfulServerTest_Author/" . $author1->ID;
|
$url = "/api/v1/RestfulServerTest_Author/" . $author1->ID;
|
||||||
$response = Director::test($url, null, null, 'GET');
|
$response = Director::test($url, null, null, 'GET');
|
||||||
$this->assertNotContains('<RelatedPages', $response->getBody());
|
$this->assertNotContains('<RelatedPages', $response->getBody(), 'Restricts many-many with api_access=false');
|
||||||
$this->assertNotContains('<PublishedPages', $response->getBody());
|
$this->assertNotContains('<PublishedPages', $response->getBody(), 'Restricts has-many with api_access=false');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testApiAccessRelationRestrictionsOnEndpoint() {
|
||||||
|
$author1 = $this->objFromFixture('RestfulServerTest_Author','author1');
|
||||||
|
|
||||||
|
$url = "/api/v1/RestfulServerTest_Author/" . $author1->ID . "/ProfilePage";
|
||||||
|
$response = Director::test($url, null, null, 'GET');
|
||||||
|
$this->assertEquals(404, $response->getStatusCode(), 'Restricts has-one with api_access=false');
|
||||||
|
|
||||||
|
$url = "/api/v1/RestfulServerTest_Author/" . $author1->ID . "/RelatedPages";
|
||||||
|
$response = Director::test($url, null, null, 'GET');
|
||||||
|
$this->assertEquals(404, $response->getStatusCode(), 'Restricts many-many with api_access=false');
|
||||||
|
|
||||||
|
$url = "/api/v1/RestfulServerTest_Author/" . $author1->ID . "/PublishedPages";
|
||||||
|
$response = Director::test($url, null, null, 'GET');
|
||||||
|
$this->assertEquals(404, $response->getStatusCode(), 'Restricts has-many with api_access=false');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApiAccessWithPUT() {
|
public function testApiAccessWithPUT() {
|
||||||
|
@ -56,9 +56,11 @@ RestfulServerTest_AuthorRating:
|
|||||||
WriteProtectedField: Dont overwrite me
|
WriteProtectedField: Dont overwrite me
|
||||||
SecretField: Dont look at me!
|
SecretField: Dont look at me!
|
||||||
Author: =>RestfulServerTest_Author.author1
|
Author: =>RestfulServerTest_Author.author1
|
||||||
|
SecretRelation: =>RestfulServerTest_Author.author1
|
||||||
rating2:
|
rating2:
|
||||||
Rating: 5
|
Rating: 5
|
||||||
Author: =>RestfulServerTest_Author.author1
|
Author: =>RestfulServerTest_Author.author1
|
||||||
|
SecretRelation: =>RestfulServerTest_Author.author1
|
||||||
RestfulServerTest_SecretThing:
|
RestfulServerTest_SecretThing:
|
||||||
thing1:
|
thing1:
|
||||||
Name: Unspeakable
|
Name: Unspeakable
|
Loading…
Reference in New Issue
Block a user