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:
Ingo Schommer 2011-11-13 13:54:29 +01:00
parent ec47cc1286
commit d30b4b1d00
3 changed files with 68 additions and 19 deletions

View File

@ -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);
}
}

View File

@ -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() {

View File

@ -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