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();
|
||||
|
||||
$responseFormatter = $this->getResponseDataFormatter();
|
||||
$responseFormatter = $this->getResponseDataFormatter($className);
|
||||
if(!$responseFormatter) return $this->unsupportedMediaType();
|
||||
|
||||
// $obj can be either a DataObject or a SS_List,
|
||||
@ -229,6 +229,9 @@ class RestfulServer extends Controller {
|
||||
if($relationName) {
|
||||
$obj = $this->getObjectRelationQuery($obj, $params, $sort, $limit, $relationName);
|
||||
if(!$obj) return $this->notFound();
|
||||
|
||||
// TODO Avoid creating data formatter again for relation class (see above)
|
||||
$responseFormatter = $this->getResponseDataFormatter($obj->dataClass());
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -270,8 +273,7 @@ class RestfulServer extends Controller {
|
||||
} else {
|
||||
$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}.
|
||||
*
|
||||
* @param boolean $includeAcceptHeader Determines wether to inspect and prioritize any HTTP Accept headers
|
||||
* @param String Classname of a DataObject
|
||||
* @return DataFormatter
|
||||
*/
|
||||
protected function getDataFormatter($includeAcceptHeader = false) {
|
||||
protected function getDataFormatter($includeAcceptHeader = false, $className = null) {
|
||||
$extension = $this->request->getExtension();
|
||||
$contentTypeWithEncoding = $this->request->getHeader('Content-Type');
|
||||
preg_match('/([^;]*)/',$contentTypeWithEncoding, $contentTypeMatches);
|
||||
$contentType = $contentTypeMatches[0];
|
||||
$accept = $this->request->getHeader('Accept');
|
||||
$mimetypes = $this->request->getAcceptMimetypes();
|
||||
if(!$className) $className = $this->urlParams['ClassName'];
|
||||
|
||||
// get formatter
|
||||
if(!empty($extension)) {
|
||||
@ -306,9 +310,9 @@ class RestfulServer extends Controller {
|
||||
// set custom fields
|
||||
if($customAddFields = $this->request->getVar('add_fields')) $formatter->setCustomAddFields(explode(',',$customAddFields));
|
||||
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)) {
|
||||
$formatter->setCustomAddFields(array_intersect((array)$formatter->getCustomAddFields(), (array)$apiAccess['view']));
|
||||
if($formatter->getCustomFields()) {
|
||||
@ -331,12 +335,20 @@ class RestfulServer extends Controller {
|
||||
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->canEdit()) return $this->permissionFailure();
|
||||
|
||||
$reqFormatter = $this->getRequestDataFormatter();
|
||||
$reqFormatter = $this->getRequestDataFormatter($className);
|
||||
if(!$reqFormatter) return $this->unsupportedMediaType();
|
||||
|
||||
$responseFormatter = $this->getResponseDataFormatter();
|
||||
$responseFormatter = $this->getResponseDataFormatter($className);
|
||||
if(!$responseFormatter) return $this->unsupportedMediaType();
|
||||
|
||||
$obj = $this->updateDataObject($obj, $reqFormatter);
|
||||
@ -419,10 +431,10 @@ class RestfulServer extends Controller {
|
||||
if(!singleton($className)->canCreate()) return $this->permissionFailure();
|
||||
$obj = new $className();
|
||||
|
||||
$reqFormatter = $this->getRequestDataFormatter();
|
||||
$reqFormatter = $this->getRequestDataFormatter($className);
|
||||
if(!$reqFormatter) return $this->unsupportedMediaType();
|
||||
|
||||
$responseFormatter = $this->getResponseDataFormatter();
|
||||
$responseFormatter = $this->getResponseDataFormatter($className);
|
||||
|
||||
$obj = $this->updateDataObject($obj, $reqFormatter);
|
||||
|
||||
@ -520,8 +532,17 @@ class RestfulServer extends Controller {
|
||||
protected function getObjectRelationQuery($obj, $params, $sort, $limit, $relationName) {
|
||||
// The relation method will return a DataList, that getSearchQuery subsequently manipulates
|
||||
if($obj->hasMethod($relationName)) {
|
||||
$query = $obj->$relationName();
|
||||
return $this->getSearchQuery($query->dataClass(), $params, $sort, $limit, $query);
|
||||
if($relationClass = $obj->has_one($relationName)) {
|
||||
$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() {
|
||||
$author1 = $this->objFromFixture('RestfulServerTest_Author','author1');
|
||||
$rating1 = $this->objFromFixture('RestfulServerTest_AuthorRating','rating1');
|
||||
|
||||
$url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID;
|
||||
@ -332,15 +333,40 @@ class RestfulServerTest extends SapphireTest {
|
||||
$this->assertNotContains('<SecretRelation>', $response->getBody(),
|
||||
'"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');
|
||||
|
||||
$url = "/api/v1/RestfulServerTest_Author/" . $author1->ID;
|
||||
$response = Director::test($url, null, null, 'GET');
|
||||
$this->assertNotContains('<RelatedPages', $response->getBody());
|
||||
$this->assertNotContains('<PublishedPages', $response->getBody());
|
||||
$this->assertNotContains('<RelatedPages', $response->getBody(), 'Restricts many-many with api_access=false');
|
||||
$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() {
|
||||
|
@ -56,9 +56,11 @@ RestfulServerTest_AuthorRating:
|
||||
WriteProtectedField: Dont overwrite me
|
||||
SecretField: Dont look at me!
|
||||
Author: =>RestfulServerTest_Author.author1
|
||||
SecretRelation: =>RestfulServerTest_Author.author1
|
||||
rating2:
|
||||
Rating: 5
|
||||
Author: =>RestfulServerTest_Author.author1
|
||||
SecretRelation: =>RestfulServerTest_Author.author1
|
||||
RestfulServerTest_SecretThing:
|
||||
thing1:
|
||||
Name: Unspeakable
|
Loading…
Reference in New Issue
Block a user