The configuration that defines a REST service does not support the Modular Authentication System.

When defining a new REST resource, we should be able to define which authentication methods it does support in the same way as we do for routes. For example:

resources:
  'entity:node':
    GET:
      supported_formats:
        - json
      supported_auth:
        - oauth

At the moment there is no way for a developer to add authentication to a REST resource.

A patch will be posted in the following comment to address this.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

juampynr’s picture

Status: Active » Needs review
FileSize
5.42 KB

Here it is.

It adds support to the _auth setting when defining REST resources at rest.settings.yml.

It also implements a test where a REST resource is added which only supports HTTP authentication and then we request it anonymously and then using an authenticated user to test it.

I had to copy the method basicAuthGet() from HttpBasicTest into RESTTestBase since I needed it. I am not sure if we should move it up to WebTestBase.

juampynr’s picture

Here is an updated version where I am removing devel module from the list of modules to enable in the test (doh!).

juampynr’s picture

This patch changes the authentication methods in a REST resource to supported_auth, as suggested by @klausi.

Crell’s picture

Issue tags: +API addition

Tagging. I think this is a necessary addition. Will review later.

juampynr’s picture

Reminder...

juampynr’s picture

Related: this module will make use of this piece of logic to define authentication for REST resources:

https://drupal.org/project/restui

klausi’s picture

Status: Needs review » Needs work
+++ b/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php
@@ -72,6 +72,10 @@ public function dynamicRoutes(RouteBuildEvent $event) {
+          // Check authentication.
+          if (is_array($enabled_methods[$method]['supported_auth']) && !empty($enabled_methods[$method]['supported_auth'])) {
+            $route->setOption('_auth', $enabled_methods[$method]['supported_auth']);
+          }

Insufficient comment, should be "Check if there are authentication provider restrictions in the configuration and apply them to the route."

+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,96 @@
+    // @todo once EntityNG is implemented for other entity types expand this at
+    // least to nodes and users.

In this case I think it is enough to test one entity type.

+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,96 @@
+      //$this->enableService('entity:' . $entity_type, 'GET');

This line should be removed?

+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,96 @@
+      // Attempt to read it over the REST API.
+      $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, $this->defaultMimeType);
+      $this->assertResponse('403', 'HTTP response code is 403 when the request is not authenticated.');
+      // Now read it authenticating the request.
+      $response = $this->basicAuthGet('entity/' . $entity_type . '/' . $entity->id(), $account->getUsername(), $account->pass_raw);
+      $this->assertResponse('200', 'HTTP response code is 200 for successfuly authenticated requests.');

Unclear comments. The first should be "Try to read the resource with session cookie authentication, which is not enabled and should not work."

The second one: "Now read it with the Basic authentication which is enabled and should work."

And we should have a test case here with a not authenticated request (anonymous user), which should also return 403.

+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,96 @@
+   * Does HTTP basic auth request.

Should be "Performs a HTTP request with Basic authentication.

juampynr’s picture

Assigned: Unassigned » juampynr
Status: Needs work » Needs review
FileSize
3.71 KB
5.89 KB

Thanks for the review. Here it is.

klausi’s picture

Status: Needs review » Needs work
+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,102 @@
+  public static function getInfo() {
+    return array(
+      'name' => 'Resource authentication',
+      'description' => 'Tests access to authenticated resources.',
+      'group' => 'REST',
+    );
+  }

description should be "Tests authentication provider restrictions."

+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,102 @@
+    // Define the entity types we want to test.
+    $entity_types = array('entity_test');
+    foreach ($entity_types as $entity_type) {

So the foreach loop is not necessary anymore.

+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,102 @@
+      // Create a user account that has the required permissions to read
+      // resources via the REST API, but the request is not authenticated.
+      $permissions = $this->entityPermissions($entity_type, 'view');
+      $permissions[] = 'restful get entity:' . $entity_type;
+      $account = $this->drupalCreateUser($permissions);
+      $this->drupalLogin($account);

Wrong comment: the request is authenticated, but with session cookies.

juampynr’s picture

Status: Needs work » Needs review
FileSize
6.05 KB
4.33 KB

Here it is.

Since I removed the loop, the interdiff is not that useful. I am adding it anyway.

klausi’s picture

Title: Allow authentication of REST resources » Restrict authentication provider for REST resources
Status: Needs review » Needs work
+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,102 @@
+/**
+ * Tests authenticated operations on test entities, nodes and users.
+ */

No tests on users and nodes present.

+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,102 @@
+  public static function getInfo() {

@{inheritdoc} missing.

+++ b/core/modules/rest/lib/Drupal/rest/Tests/AuthTest.php
@@ -0,0 +1,102 @@
+    // Try to read the resource with session cookie authentication, which is
+    // not enabled and should not work.
+    $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, $this->defaultMimeType);
+    $this->assertResponse('403', 'HTTP response code is 403 when the request is not authenticated.');
+
+    // Now read it with the Basic authentication which is enabled and should
+    // work.
+    $response = $this->basicAuthGet('entity/' . $entity_type . '/' . $entity->id(), $account->getUsername(), $account->pass_raw);
+    $this->assertResponse('200', 'HTTP response code is 200 for successfuly authenticated requests.');

I think we should call $this->drupalLogout() after the session cookie test, because the basicAuthGet request sends cookies and Basic auth credentials currently.

And we are missing an example in rest.settings.yml, same as we have there for supported formats.

juampynr’s picture

Issue summary: View changes

Updated issue summary.

juampynr’s picture

Status: Needs work » Needs review
FileSize
6.75 KB
1.81 KB

Done!

Status: Needs review » Needs work
Issue tags: -API addition

The last submitted patch, drupal-allow-auth-on-rest-resources-2054187-12.patch, failed testing.

juampynr’s picture

Status: Needs work » Needs review
Issue tags: +API addition
Crell’s picture

  1. @@ -14,3 +14,13 @@ resources:
    +#
    +# To enable only specific authentication methods for an operation, list them
    +# at supported_auth.
    +# For example, the following config only allows Basic HTTP authenticated
    +# requests for the POST method on the node entity.
    

    We should probably document here what happens if none are set. (ie, I believe all are acceptable?)

  2. @@ -0,0 +1,106 @@
    +    $this->assertText(t('A fatal error occurred: No authentication credentials provided.'));
    

    No t() in test classes. WebTests can use String::format() if they need placeholders, but in this case we don't even need that.

  3. @@ -0,0 +1,106 @@
    +    $this->assertResponse('403', 'HTTP response code is 403 when the request is not authenticated.');
    

    This error happens when the request is not AUTHORIZED, but has been authenticated. (That is, we know who you are, we just don't like you.)

  4. @@ -0,0 +1,106 @@
    +    $this->assertResponse('200', 'HTTP response code is 200 for successfuly authenticated requests.');
    

    As above, this should be "authorized".

klausi’s picture

Status: Needs review » Needs work

Needs work per #15.

juampynr’s picture

Status: Needs work » Needs review
FileSize
6.57 KB
1.93 KB

I have verified that when a route does not define authentication methods, only cookie authentication is available. Let's discuss this at #2064009: Review default authentication logic when no authentication methods are defined.

Made the rest of the suggested changes.

klausi’s picture

Status: Needs review » Reviewed & tested by the community

The ->curlClose() is not really necessary, but it does not hurt either. I think the comment in the YAML file is good enough, it is the same as for supported formats, so it should make sense as it is now.

Dries’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record

Committed to 8.x. Let's add a change notification. Keep up the great work.

moshe weitzman’s picture

Nice work... also, please update the docs at https://drupal.org/documentation/modules/rest

alexpott’s picture

Committed 4ca797a and 2cfbc18 and pushed to 8.x because a little extra got in with this change.

klausi’s picture

Title: Restrict authentication provider for REST resources » Change notice: Restrict authentication provider for REST resources
Priority: Normal » Critical
Status: Needs work » Active
juampynr’s picture

Added change record.

I have added an example of use at the REST docs page, but I also want to add there a cURL example to create a user. I need to investigate a little bit more since the following snippet returns "Method not allowed":


//set POST variables
$data = array(
  'name' => urlencode('somename'),
  'pass'=> urlencode('somepass'),
);
$data_string = json_encode($data);

$ch = curl_init();
curl_setopt_array($ch, array(
  CURLOPT_URL => 'http://d8b.local/entity/user',
  CURLOPT_RETURNTRANSFER => TRUE,
  CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
  CURLOPT_USERPWD => 'admin' . ':' . 'admin',
  CURLOPT_NOBODY => FALSE,
  CURLOPT_CUSTOMREQUEST, "POST",
  CURLOPT_POSTFIELDS, $data_string,
  CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'Content-Length: ' . strlen($data_string)),
));
print_r(curl_exec($ch));
klausi’s picture

"Currently, POSTing is only supported with media type application/hal+json. Enable HAL module to support this media type." https://drupal.org/documentation/modules/rest

linclark’s picture

juampy says:

I have verified that when a route does not define authentication methods, only cookie authentication is available.

klausi says:

I think the comment in the YAML file is good enough, it is the same as for supported formats, so it should make sense as it is now.

I do not understand how these two statements correspond. Available formats are all enabled by default. Available auth is not.

If we only have two keys in the config file, and they share a patten as 'supported_formats' and 'supported_auth' do, they should at least work in a consistent way. I'm fine with making it so that you have to define your supported formats. In fact, I think that could make it clearer to devs what is going on.

juampynr’s picture

Status: Active » Fixed

@klausi added an example to post a node using HTTP basic authentication. So the documentation is up to date now.

https://drupal.org/node/1978890

Marking as fixed.

Crell’s picture

Title: Change notice: Restrict authentication provider for REST resources » Restrict authentication provider for REST resources
Issue tags: -Needs change record
klausi’s picture

Automatically closed -- issue fixed for 2 weeks with no activity.

Anonymous’s picture

Issue summary: View changes

Updated issue summary.