Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
  • Loading branch information...
commit d30b4b1d0058ed771cdd7ada40d16a0be7719dc7 1 parent ec47cc1
@chillu chillu authored
View
53 api/RestfulServer.php
@@ -214,7 +214,7 @@ protected function getHandler($className, $id, $relationName) {
$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 @@ protected function getHandler($className, $id, $relationName) {
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 @@ protected function getSearchQuery($className, $params = null, $sort = null, $lim
} else {
$searchContext = singleton($className)->getDefaultSearchContext();
}
-
- return $searchContext->getQuery($params, $sort, $limit, $existingQuery);
+ return $searchContext->getQuery($params, $sort, $limit, $existingQuery);
}
/**
@@ -279,15 +281,17 @@ protected function getSearchQuery($className, $params = null, $sort = null, $lim
* 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 @@ protected function getDataFormatter($includeAcceptHeader = false) {
// 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 @@ protected function getDataFormatter($includeAcceptHeader = false) {
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 @@ protected function putHandler($className, $id) {
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 @@ protected function postHandler($className, $id, $relation) {
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 @@ protected function getObjectsQuery($className, $params, $sort, $limit) {
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));
@sminnee Owner
sminnee added a note

I don't like this, but it stems from a hole in the ORM API. A discussion has been kicked off here:
http://groups.google.com/group/silverstripe-dev/t/276f592cdb74e7c5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ } 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
32 tests/api/RestfulServerTest.php
@@ -305,6 +305,7 @@ public function testXMLValueFormatting() {
}
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 @@ public function testApiAccessFieldRestrictions() {
$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
2  tests/api/RestfulServerTest.yml
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.