restws.test in RESTful Web Services 7
Same filename and directory in other branches
RESTful web services tests.
File
restws.testView source
<?php
/**
* @file
* RESTful web services tests.
*/
class RestWSTestCase extends DrupalWebTestCase {
static function getInfo() {
return array(
'name' => 'RESTful web services tests',
'description' => 'Tests CRUD operations via the REST web service.',
'group' => 'Services',
);
}
function setUp() {
parent::setUp('restws');
}
/**
* CRUD tests for nodes.
*/
function testCRUD() {
// Test Read.
$title = $this
->randomName(8);
$node = $this
->drupalCreateNode(array(
'title' => $title,
));
$account = $this
->drupalCreateUser(array(
'access resource node',
));
$this
->drupalLogin($account);
$result = $this
->httpRequest('node/' . $node->nid . '.json', 'GET', $account);
$node_array = drupal_json_decode($result);
$this
->assertEqual($node->title, $node_array['title'], 'Node title was received correctly.');
$this
->assertResponse('200', 'HTTP response code is correct.');
$this
->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');
// Test Create.
$account = $this
->drupalCreateUser(array(
'access content',
'bypass node access',
'access resource node',
));
$title = $this
->randomName(8);
$new_node = array(
'body' => array(
LANGUAGE_NONE => array(
array(),
),
),
'title' => $title,
'type' => 'page',
'author' => $account->uid,
);
$json = drupal_json_encode($new_node);
$result = $this
->httpRequest('node', 'PUT', $account, $json);
$result_array = drupal_json_decode($result);
$nid = $result_array['id'];
$node = node_load($nid);
$this
->assertEqual($title, $node->title, 'Node title in DB is equal to the new title.');
$this
->assertResponse('201', 'HTTP response code is correct.');
$this
->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');
// Test Update.
$new_title = $this
->randomName(8);
$json = drupal_json_encode(array(
'title' => $new_title,
));
$this
->httpRequest('node/' . $node->nid, 'POST', $account, $json);
// Clear the static cache, otherwise we won't see the update.
$node = node_load($node->nid, NULL, TRUE);
$this
->assertEqual($new_title, $node->title, 'Node title in DB is equal to the updated title.');
$this
->assertResponse('200', 'HTTP response code is correct.');
$this
->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');
// Test delete.
$this
->httpRequest('node/' . $node->nid, 'DELETE', $account);
// Clear the static cache, otherwise we won't see the update.
$node = node_load($node->nid, NULL, TRUE);
$this
->assertFalse($node, 'Node is not in the DB anymore.');
$this
->assertResponse('200', 'HTTP response code is correct.');
$this
->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');
}
/**
* Tests bad requests.
*/
function testBadRequests() {
// Assure that nodes without types won't be created.
$account = $this
->drupalCreateUser(array(
'access content',
'bypass node access',
'access resource node',
'administer users',
));
$title = $this
->randomName(8);
$new_node = array(
'body' => array(
LANGUAGE_NONE => array(
array(),
),
),
'title' => $title,
);
$json = drupal_json_encode($new_node);
$result = $this
->httpRequest('node', 'PUT', $account, $json);
$node = entity_load('node', FALSE, array(
'title' => $title,
));
$this
->assertEqual(count($node), 0, "Node wasn't created");
$this
->assertResponse('406', 'Missing bundle: type');
}
/**
* Tests access to restricted input formats.
*/
public function testBadInputFormat() {
module_enable(array(
'php',
));
// Reset the cache of valid permissions so that the PHP code format
// permission exists.
$this
->checkPermissions(array(), TRUE);
// Assure that users can't create nodes with unauthorized input formats.
$unprivileged_account = $this
->drupalCreateUser(array(
'bypass node access',
'access resource node',
));
$title = $this
->randomName(8);
$new_node = array(
'body' => array(
'value' => $this
->randomName(30),
'format' => 'php_code',
),
'title' => $title,
'type' => 'page',
);
$json = drupal_json_encode($new_node);
$result = $this
->httpRequest('node', 'PUT', $unprivileged_account, $json);
$this
->assertResponse('403');
$this
->assertEqual($result, '403 Forbidden: Not authorized to set property body');
$node = entity_load('node', FALSE, array(
'title' => $title,
));
$this
->assertEqual(count($node), 0, "Node with unauthorized input format wasn't created");
// Check that the format is allowed if the permission is present.
$privileged_account = $this
->drupalCreateUser(array(
'bypass node access',
'access resource node',
'use text format php_code',
));
$this
->httpRequest('node', 'PUT', $privileged_account, $json);
$this
->assertResponse('201');
$node = entity_load('node', FALSE, array(
'title' => $title,
));
$this
->assertEqual(count($node), 1, "Node was created");
$node = reset($node);
$this
->assertEqual($node->body[LANGUAGE_NONE][0]['value'], $new_node['body']['value'], 'The new node body has the correct value');
$this
->assertEqual($node->body[LANGUAGE_NONE][0]['format'], 'php_code', 'The new node has the correct format');
// Check that users can't update nodes with unauthorized input formats.
$node->body[LANGUAGE_NONE][0]['format'] = 'filtered_html';
node_save($node);
$new_body = $this
->randomName(30);
$update = array(
'body' => array(
'value' => $new_body,
'format' => 'php_code',
),
);
$json = drupal_json_encode($update);
$result = $this
->httpRequest('node/1', 'POST', $unprivileged_account, $json);
$this
->assertResponse('403');
$this
->assertEqual($result, '403 Forbidden: Not authorized to set property body');
$node = node_load(1, NULL, TRUE);
$this
->assertNotEqual($node->body[LANGUAGE_NONE][0]['value'], $new_body);
$this
->assertEqual($node->body[LANGUAGE_NONE][0]['format'], 'filtered_html');
// Check that the format is allowed if the permission is present.
$this
->httpRequest('node/1', 'POST', $privileged_account, $json);
$this
->assertResponse('200');
$node = node_load(1, NULL, TRUE);
$this
->assertEqual($node->body[LANGUAGE_NONE][0]['value'], $new_body);
$this
->assertEqual($node->body[LANGUAGE_NONE][0]['format'], 'php_code');
}
/**
* Test field level access restrictions.
*
* @see restws_test_field_access()
*/
public function testFieldAccess() {
module_enable(array(
'restws_test',
));
// Add text field to nodes.
$field_info = array(
'field_name' => 'field_text',
'type' => 'text',
'entity_types' => array(
'node',
),
);
field_create_field($field_info);
$instance = array(
'label' => 'Text Field',
'field_name' => 'field_text',
'entity_type' => 'node',
'bundle' => 'page',
'settings' => array(),
'required' => FALSE,
);
field_create_instance($instance);
// A user without the "administer users" permission should not be able to
// create a node with the access protected field.
$unprivileged_account = $this
->drupalCreateUser(array(
'bypass node access',
'access resource node',
));
$title = $this
->randomName(8);
$new_node = array(
'title' => $title,
'type' => 'page',
'field_text' => 'test',
);
$json = drupal_json_encode($new_node);
$this
->httpRequest('node', 'PUT', $unprivileged_account, $json);
$this
->assertResponse('403');
$nodes = entity_load('node', FALSE, array(
'title' => $title,
));
$this
->assertEqual(count($nodes), 0, "Node with access protected field wasn't created");
// Test again with the additional permission, this should work now.
$privileged_account = $this
->drupalCreateUser(array(
'bypass node access',
'access resource node',
'administer users',
));
$this
->httpRequest('node', 'PUT', $privileged_account, $json);
$this
->assertResponse('201');
$node = node_load(1, NULL, TRUE);
$this
->assertEqual($node->field_text[LANGUAGE_NONE][0]['value'], 'test');
// Update test: unpriviledged users should not be able to change the
// protected field.
$update = array(
'field_text' => 'newvalue',
);
$json = drupal_json_encode($update);
$result = $this
->httpRequest('node/1', 'POST', $unprivileged_account, $json);
$this
->assertResponse('403');
$this
->assertEqual($result, '403 Forbidden: Not authorized to set property field_text');
$node = node_load(1, NULL, TRUE);
$this
->assertEqual($node->field_text[LANGUAGE_NONE][0]['value'], 'test');
// Check that the update is allowed if the permission is present.
$this
->httpRequest('node/1', 'POST', $privileged_account, $json);
$this
->assertResponse('200');
$node = node_load(1, NULL, TRUE);
$this
->assertEqual($node->field_text[LANGUAGE_NONE][0]['value'], 'newvalue');
}
/**
* Tests using the xml formatter.
*/
function testXmlFormatter() {
// Test Read.
$account = $this
->drupalCreateUser(array(
'access content',
'bypass node access',
'access resource node',
));
$this
->drupalLogin($account);
$title = $this
->randomName(8);
$node = $this
->drupalCreateNode(array(
'title' => $title,
));
$result = $this
->drupalGet("node/{$node->nid}", array(), array(
'Accept: application/xml',
));
$this
->assertRaw("<title>{$title}</title>", 'XML has been generated.');
// Test update.
$new_title = 'foo';
$result = $this
->httpRequest('node/' . $node->nid, 'POST', $account, "<node><title>{$new_title}</title></node>", 'xml');
// Clear the static cache, otherwise we won't see the update.
$node = node_load($node->nid, NULL, TRUE);
$this
->assertEqual($new_title, $node->title, 'Node title in DB is equal to the updated title.');
$this
->assertResponse('200', 'HTTP response code is correct.');
$this
->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/xml', 'HTTP content type is correct.');
}
/**
* Test requests to non-existing resources and other errors.
*/
function testErrors() {
// Read non-existing resource.
$random_nid = rand(1, 1000);
$result = $this
->httpRequest('node/' . $random_nid, 'GET');
$this
->assertResponse('404', 'HTTP response code is correct.');
// Update a node with an unknown property.
$account = $this
->drupalCreateUser(array(
'access content',
'bypass node access',
'access resource node',
));
$node = $this
->drupalCreateNode();
$property_name = $this
->randomName(8);
$json = drupal_json_encode(array(
$property_name => $property_name,
));
$result = $this
->httpRequest('node/' . $node->nid, 'POST', $account, $json);
$this
->assertEqual($result, "406 Not Acceptable: Unknown data property {$property_name}.", 'Response body is correct');
$this
->assertResponse('406', 'HTTP response code is correct.');
// Create a node with an unknown property.
$title = $this
->randomName(8);
$new_node = array(
'body' => array(
LANGUAGE_NONE => array(
array(),
),
),
'title' => $this
->randomName(8),
'type' => 'page',
'author' => $account->uid,
$property_name => $property_name,
);
$json = drupal_json_encode($new_node);
$result = $this
->httpRequest('node', 'PUT', $account, $json);
$this
->assertEqual($result, "406 Not Acceptable: Unknown data property {$property_name}.", 'Response body is correct');
$this
->assertResponse('406', 'HTTP response code is correct.');
// Simulate a CSRF attack without the required token.
$new_title = 'HACKED!';
$json = drupal_json_encode(array(
'title' => $new_title,
));
$this
->curlExec(array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_POST => TRUE,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $json,
CURLOPT_URL => url('node/' . $node->nid, array(
'absolute' => TRUE,
)),
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json',
),
));
$this
->assertResponse(403);
// Clear the static cache, otherwise we won't see the update.
$node = node_load($node->nid, NULL, TRUE);
$this
->assertNotEqual($node->title, $new_title, 'Node title was not updated in the database.');
// Simulate a cache poisoning attack where JSON could get into the page
// cache.
// Grant node resource access to anonymous users.
user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array(
'access resource node',
));
// Enable page caching.
variable_set('cache', 1);
// Reset cURL here to delete any stored request settings.
unset($this->curlHandle);
// Request the JSON representation of the node.
$this
->drupalGet("node/{$node->nid}", array(), array(
'Accept: application/json',
));
$this
->assertUrl("node/{$node->nid}.json", array(), 'Requesting a resource with JSON Accept header redirects to the .json URL.');
// Now request the HTML representation.
$result = $this
->drupalGet("node/{$node->nid}");
$content_type = $this
->drupalGetHeader('content-type');
$this
->assertNotEqual($content_type, 'application/json', 'Content type header is not JSON after requesting HTML.');
$this
->assertNull(drupal_json_decode($result), 'Response body is not JSON after requesting HTML.');
}
/**
* Test that sensitive user data is hidden for the "access user profiles"
* permission.
*/
function testUserPermissions() {
// Test other user with "acces user profiles" permission.
$test_user = $this
->drupalCreateUser();
$account = $this
->drupalCreateUser(array(
'access resource user',
'access user profiles',
));
$result = $this
->httpRequest('user/' . $test_user->uid . '.json', 'GET', $account);
$user_array = drupal_json_decode($result);
$this
->assertEqual($test_user->name, $user_array['name'], 'User name was received correctly.');
$this
->assertFalse(isset($user_array['mail']), 'User mail is not present in the response.');
$this
->assertFalse(isset($user_array['roles']), 'User roles are not present in the response.');
$this
->assertResponse('200', 'HTTP response code is correct.');
$this
->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');
// Test the own user - access to sensitive information should be allowed.
$result = $this
->httpRequest('user/' . $account->uid . '.json', 'GET', $account);
$user_array = drupal_json_decode($result);
$this
->assertEqual($account->name, $user_array['name'], 'User name was received correctly.');
$this
->assertEqual($account->mail, $user_array['mail'], 'User mail is present in the response.');
$role_keys = array_keys($account->roles);
$this
->assertEqual(sort($role_keys), sort($user_array['roles']), 'User roles are present in the response.');
$this
->assertResponse('200', 'HTTP response code is correct.');
$this
->assertEqual(curl_getinfo($this->curlHandle, CURLINFO_CONTENT_TYPE), 'application/json', 'HTTP content type is correct.');
}
/**
* Helper function to issue a HTTP request with simpletest's cURL.
*/
protected function httpRequest($url, $method, $account = NULL, $body = NULL, $format = 'json') {
if (isset($account)) {
unset($this->curlHandle);
$this
->drupalLogin($account);
}
if (in_array($method, array(
'POST',
'PUT',
'DELETE',
))) {
// GET the CSRF token first for writing requests.
$token = $this
->drupalGet('restws/session/token');
}
switch ($method) {
case 'GET':
return $this
->curlExec(array(
CURLOPT_HTTPGET => TRUE,
CURLOPT_URL => url($url, array(
'absolute' => TRUE,
)),
CURLOPT_NOBODY => FALSE,
));
case 'PUT':
return $this
->curlExec(array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => $body,
CURLOPT_URL => url($url, array(
'absolute' => TRUE,
)),
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => array(
'Content-Type: application/' . $format,
'X-CSRF-Token: ' . $token,
),
));
case 'POST':
return $this
->curlExec(array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $body,
CURLOPT_URL => url($url, array(
'absolute' => TRUE,
)),
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => array(
'Content-Type: application/' . $format,
'X-CSRF-Token: ' . $token,
),
));
case 'DELETE':
return $this
->curlExec(array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_URL => url($url, array(
'absolute' => TRUE,
)),
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => array(
'X-CSRF-Token: ' . $token,
),
));
}
}
}
Classes
Name | Description |
---|---|
RestWSTestCase | @file RESTful web services tests. |