Skip to content
Snippets Groups Projects
Commit 295c7444 authored by Patrick Kenny's avatar Patrick Kenny
Browse files

Issue #3284838 by ptmkenny, frob, cspitzlay: User-configurable authentication...

Issue #3284838 by ptmkenny, frob, cspitzlay: User-configurable authentication (add cookie + jwt_auth)
parent fd40fa26
Branches
Tags
1 merge request!303.x add config for cookie/jwt auth and check for CSRF token
Pipeline #323978 passed
Showing
with 604 additions and 121 deletions
basic_auth: TRUE
cookie: FALSE
jwt: FALSE
oauth2: TRUE
# Schema for the configuration files of the JSON-RPC module.
jsonrpc.settings:
type: config_object
label: 'JSON-RPC settings'
mapping:
basic_auth:
type: boolean
label: 'Allow access via basic authentication'
cookie:
type: boolean
label: 'Allow access via cookies'
jwt:
type: boolean
label: 'Allow access via JWT'
oauth2:
type: boolean
label: 'Allow access via OAuth 2.0'
name: JSON-RPC 2.0
type: module
description: Infrastructure for JSON-RPC 2.0 compliant web services.
core_version_requirement: ^9 || ^10 || ^11
configure: jsonrpc.settings
core_version_requirement: ^10.2 || ^11
package: Web services
<?php
/**
* @file
* Hook update implementations for the JSON-RPC module.
*/
declare(strict_types=1);
use Drupal\jsonrpc\Enum\JsonRpcSetting;
/**
* Add config for access control to the JSON-RPC module.
*/
function jsonrpc_update_2001(): void {
$config_factory = \Drupal::configFactory();
$config = $config_factory->getEditable('jsonrpc.configuration');
$config->set(JsonRpcSetting::BasicAuth->value, TRUE);
$config->set(JsonRpcSetting::Cookie->value, FALSE);
$config->set(JsonRpcSetting::OAuth2->value, TRUE);
$config->save(TRUE);
}
use jsonrpc services:
title: 'Use JSON-RPC services.'
administer jsonrpc:
title: 'Administer JSON-RPC.'
......@@ -6,4 +6,12 @@ jsonrpc.handler:
requirements:
_permission: 'use jsonrpc services'
options:
_auth: ['basic_auth', 'oauth2']
# Allowed authorization methods are set on the module configuration page.
_auth: []
jsonrpc.settings:
path: '/admin/config/system/jsonrpc'
defaults:
_title: 'JSON-RPC'
_form: 'Drupal\jsonrpc\Form\JsonRpcConfigurationForm'
requirements:
_permission: 'administer jsonrpc'
......@@ -14,6 +14,11 @@ services:
'@renderer',
'@Drupal\jsonrpc\Exception\ErrorHandler',
]
jsonrpc.route_subscriber:
class: Drupal\jsonrpc\Routing\JsonRpcRouteSubscriber
arguments: ['@config.factory']
tags:
- { name: event_subscriber }
jsonrpc.schema_validator:
class: JsonSchema\Validator
jsonrpc.options_request_listener:
......
name: JSON-RPC 2.0 Core
type: module
description: JSON-RPC 2.0 services for Drupal core.
core_version_requirement: ^9 || ^10 || ^11
core_version_requirement: ^10.2 || ^11
package: Web services
dependencies:
- jsonrpc:jsonrpc
......@@ -5,10 +5,13 @@ declare(strict_types=1);
namespace Drupal\Tests\jsonrpc_core\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\Tests\jsonrpc\Functional\JsonRpcTestBase;
use Drupal\jsonrpc\Enum\JsonRpcSetting;
use GuzzleHttp\RequestOptions;
/**
* Test turning the maintenance mode on or off using JSON RPC.
* Test turning the maintenance mode on or off using JSON-RPC.
*
* @group jsonrpc
*/
......@@ -25,11 +28,11 @@ class MaintenanceModeEnabledTest extends JsonRpcTestBase {
];
/**
* Tests enabling the maintenance mode.
* The POST request to enable maintenance mode.
*
* @var array
*/
public function testEnablingMaintenanceMode(): void {
$enabled_request = [
protected array $enableRequest = [
'jsonrpc' => '2.0',
'method' => 'maintenance_mode.isEnabled',
'params' => [
......@@ -38,17 +41,35 @@ class MaintenanceModeEnabledTest extends JsonRpcTestBase {
'id' => 'maintenance_mode_enabled',
];
// Assert that anonymous users are not able to enable the maintenance page.
$response = $this->postRpc($enabled_request);
$this->assertSame(401, $response->getStatusCode());
/**
* The POST request to disabled maintenance mode.
*
* @var array
*/
protected array $disableRequest = [
'jsonrpc' => '2.0',
'method' => 'maintenance_mode.isEnabled',
'params' => [
'enabled' => FALSE,
],
'id' => 'maintenance_mode_disabled',
];
// Assign correct permission and login.
$account = $this->createUser(['administer site configuration'], NULL, TRUE);
/**
* Tests enabling maintenance mode.
*
* @param \Drupal\jsonrpc\Enum\JsonRpcSetting $auth_method
* The auth method to use for the test.
*/
private function enableMaintenanceModeTestFlow(JsonRpcSetting $auth_method): void {
// Assert that anonymous users are not able to enable the maintenance page.
$anon_response = $this->postRpc($this->enableRequest);
$this->assertSame(401, $anon_response->getStatusCode());
// Retry request with basic auth.
$response = $this->postRpc($enabled_request, $account);
$this->assertSame(200, $response->getStatusCode());
$parsed_body = Json::decode($response->getBody());
// Retry request with auth.
$auth_response = $this->postRpc($this->enableRequest, $this->adminUser, $auth_method);
$this->assertSame(200, $auth_response->getStatusCode());
$parsed_body = Json::decode($auth_response->getBody());
$expected = [
'jsonrpc' => '2.0',
'id' => 'maintenance_mode_enabled',
......@@ -57,22 +78,17 @@ class MaintenanceModeEnabledTest extends JsonRpcTestBase {
$this->assertEquals($expected, $parsed_body);
// Assert maintenance mode is enabled.
// If using cookie auth, we need to log out first because we are an admin.
if ($auth_method === JsonRpcSetting::Cookie) {
$this->drupalLogout();
}
$this->drupalGet('/jsonrpc');
$this->assertEquals('Site under maintenance', $this->cssSelect('main h1')[0]->getText());
// Send request to disable maintenance mode.
$disabled_request = [
'jsonrpc' => '2.0',
'method' => 'maintenance_mode.isEnabled',
'params' => [
'enabled' => FALSE,
],
'id' => 'maintenance_mode_disabled',
];
$response = $this->postRpc($disabled_request, $account);
$this->assertSame(200, $response->getStatusCode());
$parsed_body = Json::decode($response->getBody());
$disabled_response = $this->postRpc($this->disableRequest, $this->adminUser, $auth_method);
$this->assertSame(200, $disabled_response->getStatusCode());
$parsed_body = Json::decode($disabled_response->getBody());
$expected = [
'jsonrpc' => '2.0',
'id' => 'maintenance_mode_disabled',
......@@ -81,8 +97,52 @@ class MaintenanceModeEnabledTest extends JsonRpcTestBase {
$this->assertEquals($expected, $parsed_body);
// Assert maintenance mode is disabled.
// If using cookie auth, we need to log out first because we are an admin.
if ($auth_method === JsonRpcSetting::Cookie) {
$this->drupalLogout();
}
$this->drupalGet('/jsonrpc');
$this->assertNotEquals('Site under maintenance', $this->cssSelect('main h1')[0]->getText());
}
/**
* Tests enabling maintenance mode with basic auth.
*/
public function testEnablingMaintenanceModeBasicAuth(): void {
$this->enableBasicAuth();
$this->enableMaintenanceModeTestFlow(JsonRpcSetting::BasicAuth);
}
/**
* Tests enabling maintenance mode with cookie auth.
*/
public function testEnablingMaintenanceModeCookie(): void {
$this->enableCookieAuth();
$this->enableMaintenanceModeTestFlow(JsonRpcSetting::Cookie);
}
/**
* Tests that enabling maintenance mode by cookie requires a CSRF token.
*/
public function testEnablingMaintenanceModeCookieNoCsrfToken(): void {
$this->enableCookieAuth();
$url = $this->buildUrl(Url::fromRoute('jsonrpc.handler'));
$request_options = [
RequestOptions::HTTP_ERRORS => FALSE,
RequestOptions::ALLOW_REDIRECTS => FALSE,
RequestOptions::JSON => $this->enableRequest,
];
// For cookies, we need to log in get the cookie.
$this->drupalLogin($this->adminUser);
// Include cookies, but do not include a CSRF token.
$request_options[RequestOptions::COOKIES] = $this->getSessionCookies();
$client = $this->getHttpClient();
$no_csrf_token_response = $client->request('POST', $url, $request_options);
$this->assertSame(403, $no_csrf_token_response->getStatusCode());
}
}
......@@ -6,6 +6,7 @@ namespace Drupal\Tests\jsonrpc_core\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Tests\jsonrpc\Functional\JsonRpcTestBase;
use Drupal\jsonrpc\Enum\JsonRpcSetting;
/**
* Test turning the maintenance mode on or off using JSON RPC.
......@@ -27,8 +28,11 @@ class PluginsTest extends JsonRpcTestBase {
/**
* Tests the plugin list.
*
* @param \Drupal\jsonrpc\Enum\JsonRpcSetting $auth_method
* The auth method to use for the test.
*/
public function testPlugins(): void {
public function pluginTestFlow(JsonRpcSetting $auth_method): void {
// 1. Test without a pager.
$rpc_request = [
'jsonrpc' => '2.0',
......@@ -43,11 +47,8 @@ class PluginsTest extends JsonRpcTestBase {
$response = $this->getRpc($rpc_request);
$this->assertSame(401, $response->getStatusCode());
// Assign correct permission and login.
$account = $this->createUser(['administer site configuration'], NULL, TRUE);
// Retry request with auth.
$response = $this->getRpc($rpc_request, $account);
$response = $this->getRpc($rpc_request, $this->adminUser, $auth_method);
$this->assertSame(200, $response->getStatusCode());
$parsed_body = Json::decode($response->getBody());
$this->assertArrayHasKey('result', $parsed_body, 'Could not find results');
......@@ -64,7 +65,7 @@ class PluginsTest extends JsonRpcTestBase {
'page' => ['limit' => 2, 'offset' => 1],
],
];
$response = $this->getRpc($rpc_request, $account);
$response = $this->getRpc($rpc_request, $this->adminUser, $auth_method);
$this->assertSame(200, $response->getStatusCode());
$parsed_body = Json::decode($response->getBody());
$this->assertCount(2, $parsed_body['result']);
......@@ -79,7 +80,7 @@ class PluginsTest extends JsonRpcTestBase {
'page' => ['limit' => 2, 'offset' => 1],
],
];
$response = $this->getRpc($rpc_request, $account);
$response = $this->getRpc($rpc_request, $this->adminUser, $auth_method);
$this->assertSame(400, $response->getStatusCode());
$parsed_body = Json::decode($response->getBody());
$expected = [
......@@ -94,4 +95,20 @@ class PluginsTest extends JsonRpcTestBase {
$this->assertEquals($expected, $parsed_body);
}
/**
* Tests the plugin list with basic auth.
*/
public function testPluginListBasicAuth(): void {
$this->enableBasicAuth();
$this->pluginTestFlow(JsonRpcSetting::BasicAuth);
}
/**
* Tests the plugin list with cookie auth.
*/
public function testPluginListCookie(): void {
$this->enableCookieAuth();
$this->pluginTestFlow(JsonRpcSetting::Cookie);
}
}
name: JSON-RPC 2.0 Discovery
type: module
description: JSON-RPC 2.0 discovery features.
core_version_requirement: ^8 || ^9 || ^10 || ^11
core_version_requirement: ^10.2 || ^11
package: Web services
dependencies:
- drupal:serialization
......
<?php
declare(strict_types=1);
namespace Drupal\Tests\jsonrpc\Functional;
use Drupal\Tests\jsonrpc_discovery\Functional\JsonRpcDiscoveryFunctionalTestBase;
/**
* Tests the configuration for basic_auth.
*
* @group jsonrpc
*/
class JsonRpcDiscoveryBasicAuthTest extends JsonRpcDiscoveryFunctionalTestBase {
/**
* Tests getting the methods as an anonymous user with basic auth.
*/
public function testBasicAuthAnon(): void {
$this->enableBasicAuth();
// Anon does not have access to JSON-RPC services.
$method_url = $this->getMethodsUrl();
try {
$this->getJsonRpcMethod($method_url);
}
catch (\Exception $e) {
$this->assertStringContainsString('401 Unauthorized', $e->getMessage());
}
}
/**
* Tests getting the methods as an auth user with basic auth.
*/
public function testBasicAuthAuth(): void {
$this->enableBasicAuth();
$method_url = $this->getMethodsUrl();
$auth_response = $this->getJsonRpcMethod($method_url, $this->user);
$this->assertEquals(200, $auth_response->getStatusCode());
$this->disableBasicAuth();
try {
$this->getJsonRpcMethod($method_url, $this->user);
}
catch (\Exception $e) {
$this->assertStringContainsString('403', $e->getMessage());
}
}
/**
* Tests getting the methods as an admin user with basic auth.
*/
public function testBasicAuthAdmin(): void {
$this->enableBasicAuth();
$method_url = $this->getMethodsUrl();
$admin_response = $this->getJsonRpcMethod($method_url, $this->adminUser);
$this->assertEquals(200, $admin_response->getStatusCode());
$this->disableBasicAuth();
try {
$this->getJsonRpcMethod($method_url, $this->adminUser);
}
catch (\Exception $e) {
$this->assertStringContainsString('403', $e->getMessage());
}
}
}
<?php
declare(strict_types=1);
namespace Drupal\Tests\jsonrpc\Functional;
use Drupal\Tests\jsonrpc_discovery\Functional\JsonRpcDiscoveryFunctionalTestBase;
use Drupal\user\UserInterface;
use GuzzleHttp\RequestOptions;
use Psr\Http\Message\ResponseInterface;
/**
* Tests the configuration for cookie auth.
*
* @group jsonrpc
*/
class JsonRpcDiscoveryCookieAuthTest extends JsonRpcDiscoveryFunctionalTestBase {
/**
* Sends a GET request to a JSON-RPC method.
*
* @param string $method_url
* The URL of the JSON-RPC method.
* @param \Drupal\user\UserInterface|null $user
* The user interface of the user account. For anon, use null.
*
* @return \Psr\Http\Message\ResponseInterface
* The response from the JSON-RPC method.
*
* @throws \GuzzleHttp\Exception\GuzzleException
*/
#[\Override]
protected function getJsonRpcMethod(string $method_url, ?UserInterface $user = NULL): ResponseInterface {
$request_options = [
RequestOptions::BODY => NULL,
RequestOptions::HEADERS => [],
];
if ($user instanceof UserInterface) {
$request_options[RequestOptions::COOKIES] = $this->getSessionCookies();
}
return \Drupal::httpClient()->get($method_url, $request_options);
}
/**
* Tests getting the methods as an anonymous user with cookie auth.
*/
public function testCookieAnon(): void {
$this->enableCookieAuth();
// Anon does not have access to JSON-RPC services.
$method_url = $this->getMethodsUrl();
try {
$this->getJsonRpcMethod($method_url);
}
catch (\Exception $e) {
$this->assertStringContainsString('401 Unauthorized', $e->getMessage());
}
}
/**
* Tests getting the methods as an auth user with cookie auth.
*/
public function testCookieAuth(): void {
$this->enableCookieAuth();
$this->drupalLogin($this->user);
$method_url = $this->getMethodsUrl();
$auth_response = $this->getJsonRpcMethod($method_url, $this->user);
$this->assertEquals(200, $auth_response->getStatusCode());
$this->disableCookieAuth();
try {
$this->getJsonRpcMethod($method_url, $this->user);
}
catch (\Exception $e) {
$this->assertStringContainsString('403', $e->getMessage());
}
}
/**
* Tests getting the methods as an admin user with cookie auth.
*/
public function testCookieAdmin(): void {
$this->enableCookieAuth();
$this->drupalLogin($this->adminUser);
$method_url = $this->getMethodsUrl();
$admin_response = $this->getJsonRpcMethod($method_url, $this->adminUser);
$this->assertEquals(200, $admin_response->getStatusCode());
$this->disableCookieAuth();
try {
$this->getJsonRpcMethod($method_url, $this->adminUser);
}
catch (\Exception $e) {
$this->assertStringContainsString('403', $e->getMessage());
}
}
}
......@@ -4,17 +4,18 @@ declare(strict_types=1);
namespace Drupal\Tests\jsonrpc_discovery\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use Drupal\Core\Url;
use Drupal\Tests\jsonrpc\Functional\JsonRpcTestBase;
use Drupal\user\UserInterface;
use GuzzleHttp\RequestOptions;
use Psr\Http\Message\ResponseInterface;
/**
* This class provides methods specifically for testing something.
*
* @group jsonrpc
*/
abstract class JsonRpcDiscoveryFunctionalTestBase extends BrowserTestBase {
abstract class JsonRpcDiscoveryFunctionalTestBase extends JsonRpcTestBase {
/**
* {@inheritdoc}
......@@ -27,40 +28,59 @@ abstract class JsonRpcDiscoveryFunctionalTestBase extends BrowserTestBase {
];
/**
* A user with authenticated permissions.
*
* @var \Drupal\user\UserInterface
* {@inheritdoc}
*/
protected UserInterface $user;
protected $defaultTheme = 'stark';
/**
* A user with admin permissions.
* Executes a request to jsonrpc/methods.
*
* @var \Drupal\user\UserInterface
* @return string
* The absolute url.
*/
protected UserInterface $adminUser;
protected function getMethodsUrl(): string {
return Url::fromRoute('jsonrpc.method_collection')
->setAbsolute()->toString();
}
/**
* {@inheritdoc}
* Provides a basic auth header.
*
* @param \Drupal\user\UserInterface $user
* The user account.
*
* @return string
* The basic auth header value formatted for Guzzle.
*/
protected $defaultTheme = 'stark';
private function getAuthForUser(UserInterface $user): string {
$name = $user->getAccountName();
$pass = $user->passRaw;
return 'Basic ' . base64_encode($name . ':' . $pass);
}
/**
* {@inheritdoc}
* Sends a GET request to a JSON-RPC method.
*
* @param string $method_url
* The URL of the JSON-RPC method.
* @param \Drupal\user\UserInterface|null $user
* The user interface of the user account. For anon, use null.
*
* @return \Psr\Http\Message\ResponseInterface
* The response from the JSON-RPC method.
*
* @throws \GuzzleHttp\Exception\GuzzleException
*/
#[\Override]
protected function setUp(): void {
parent::setUp();
// Grant authorized users permission to use JSON-RPC.
$auth_role = Role::load(RoleInterface::AUTHENTICATED_ID);
$this->grantPermissions($auth_role, ['use jsonrpc services']);
$this->user = $this->drupalCreateUser([], 'user', FALSE, ['mail' => 'user@example.com']);
$this->adminUser = $this->drupalCreateUser([], 'adminUser', TRUE, ['mail' => 'admin@example.com']);
protected function getJsonRpcMethod(string $method_url, ?UserInterface $user = NULL): ResponseInterface {
$request_options = [
RequestOptions::BODY => NULL,
RequestOptions::HEADERS => [],
];
$this->adminUser->save();
if ($user instanceof UserInterface) {
$request_options[RequestOptions::HEADERS]['Authorization'] = $this->getAuthForUser($user);
}
return \Drupal::httpClient()->get($method_url, $request_options);
}
}
......@@ -4,9 +4,7 @@ declare(strict_types=1);
namespace Drupal\Tests\jsonrpc\Functional;
use Drupal\Core\Url;
use Drupal\Tests\jsonrpc_discovery\Functional\JsonRpcDiscoveryFunctionalTestBase;
use Drupal\user\UserInterface;
use Psr\Http\Message\ResponseInterface;
/**
......@@ -17,32 +15,6 @@ use Psr\Http\Message\ResponseInterface;
class JsonRpcDiscoveryHttpTest extends JsonRpcDiscoveryFunctionalTestBase {
const PLUGINS_METHOD_NAME = 'List defined plugins';
/**
* Executes a request to jsonrpc/methods.
*
* @return string
* The absolute url.
*/
protected function getMethodsUrl(): string {
return Url::fromRoute('jsonrpc.method_collection')
->setAbsolute()->toString();
}
/**
* Provides a basic auth header.
*
* @param \Drupal\user\UserInterface $user
* The user account.
*
* @return string
* The basic auth header value formatted for Guzzle.
*/
protected function getAuthForUser(UserInterface $user): string {
$name = $user->getAccountName();
$pass = $user->passRaw;
return 'Basic ' . base64_encode($name . ':' . $pass);
}
/**
* Gets the JSON-RPC result from the response.
*
......@@ -65,10 +37,7 @@ class JsonRpcDiscoveryHttpTest extends JsonRpcDiscoveryFunctionalTestBase {
// Anon does not have access to JSON-RPC services.
$method_url = $this->getMethodsUrl();
try {
\Drupal::httpClient()->get($method_url, [
'body' => NULL,
'headers' => [],
]);
$this->getJsonRpcMethod($method_url);
}
catch (\Exception $e) {
$this->assertStringContainsString('401 Unauthorized', $e->getMessage());
......@@ -84,12 +53,7 @@ class JsonRpcDiscoveryHttpTest extends JsonRpcDiscoveryFunctionalTestBase {
$this->assertFalse($has_plugins_method_permission, 'User account has "administer site configuration" permission to access the Plugins JSON-RPC method, but it should not have this permission.');
$method_url = $this->getMethodsUrl();
$auth_response = \Drupal::httpClient()->get($method_url, [
'body' => NULL,
'headers' => [
'Authorization' => $this->getAuthForUser($this->user),
],
]);
$auth_response = $this->getJsonRpcMethod($method_url, $this->user);
$this->assertEquals(200, $auth_response->getStatusCode());
// Auth does not have access to the plugins method.
$this->assertStringNotContainsString(self::PLUGINS_METHOD_NAME, $this->getJsonRpcResultFromResponse($auth_response));
......@@ -104,12 +68,7 @@ class JsonRpcDiscoveryHttpTest extends JsonRpcDiscoveryFunctionalTestBase {
$this->assertTrue($has_plugins_method_permission, 'Admin account does not have permission to access the Plugins JSON-RPC method.');
$method_url = $this->getMethodsUrl();
$admin_response = \Drupal::httpClient()->get($method_url, [
'body' => NULL,
'headers' => [
'Authorization' => $this->getAuthForUser($this->adminUser),
],
]);
$admin_response = $this->getJsonRpcMethod($method_url, $this->adminUser);
$this->assertEquals(200, $admin_response->getStatusCode());
// Admin does have access to the plugins method.
$this->assertStringContainsString(self::PLUGINS_METHOD_NAME, $this->getJsonRpcResultFromResponse($admin_response));
......@@ -124,12 +83,7 @@ class JsonRpcDiscoveryHttpTest extends JsonRpcDiscoveryFunctionalTestBase {
$this->assertTrue($has_plugins_method_permission, 'Admin account does not have permission to access the Plugins JSON-RPC method.');
$method_url = $this->getMethodsUrl() . '/plugins.list';
$admin_response = \Drupal::httpClient()->get($method_url, [
'body' => NULL,
'headers' => [
'Authorization' => $this->getAuthForUser($this->adminUser),
],
]);
$admin_response = $this->getJsonRpcMethod($method_url, $this->adminUser);
$this->assertEquals(200, $admin_response->getStatusCode());
$this->assertStringContainsString(self::PLUGINS_METHOD_NAME, $this->getJsonRpcResultFromResponse($admin_response));
}
......
......@@ -88,7 +88,7 @@ class JsonRpcMethod extends AnnotationBase implements MethodInterface {
*/
#[\Override]
public function call(): string {
if (!isset($this->call)) {
if ($this->call === NULL) {
$this->call = 'execute';
}
return $this->call;
......
<?php
declare(strict_types=1);
namespace Drupal\jsonrpc\Enum;
/**
* Settings values for the JSON-RPC module.
*/
enum JsonRpcSetting: string {
case BasicAuth = 'basic_auth';
case Cookie = 'cookie';
case JWT = 'jwt_auth';
case OAuth2 = 'oauth2';
}
<?php
declare(strict_types=1);
namespace Drupal\jsonrpc\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\jsonrpc\Enum\JsonRpcSetting;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configures the JSON-RPC module.
*/
final class JsonRpcConfigurationForm extends ConfigFormBase {
public function __construct(
ConfigFactoryInterface $config_factory,
TypedConfigManagerInterface $typed_config_manager,
protected ModuleHandlerInterface $moduleHandler,
protected RouteBuilderInterface $routeBuilder,
) {
parent::__construct($config_factory, $typed_config_manager);
}
/**
* {@inheritdoc}
*/
#[\Override]
public static function create(ContainerInterface $container): JsonRpcConfigurationForm {
return new self(
$container->get('config.factory'),
$container->get('config.typed'),
$container->get('module_handler'),
$container->get('router.builder')
);
}
const string FORM_ID = 'jsonrpc.settings';
/**
* {@inheritdoc}
*/
#[\Override]
public function getFormId(): string {
return self::FORM_ID;
}
/**
* {@inheritdoc}
*/
#[\Override]
protected function getEditableConfigNames(): array {
return [self::FORM_ID];
}
/**
* {@inheritdoc}
*/
#[\Override]
public function buildForm(array $form, FormStateInterface $form_state): array {
$config = $this->config(self::FORM_ID);
$form[self::FORM_ID] = [
'#type' => 'details',
'#title' => $this->t('Configure JSON-RPC access'),
'#open' => TRUE,
];
$form[self::FORM_ID][JsonRpcSetting::BasicAuth->value] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow access via basic authentication'),
'#default_value' => $config->get(JsonRpcSetting::BasicAuth->value),
'#required' => FALSE,
];
$form[self::FORM_ID][JsonRpcSetting::Cookie->value] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow access via cookies'),
'#default_value' => $config->get(JsonRpcSetting::Cookie->value),
'#required' => FALSE,
];
$form[self::FORM_ID][JsonRpcSetting::JWT->value] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow access via JWT'),
'#default_value' => $config->get(JsonRpcSetting::JWT->value),
'#required' => FALSE,
];
$form[self::FORM_ID][JsonRpcSetting::OAuth2->value] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow access via basic authentication'),
'#default_value' => $config->get(JsonRpcSetting::OAuth2->value),
'#required' => FALSE,
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
#[\Override]
public function submitForm(array &$form, FormStateInterface $form_state): void {
$config = $this->config(self::FORM_ID);
$config
->set(JsonRpcSetting::BasicAuth->value, boolval($form_state->getValue(JsonRpcSetting::BasicAuth->value)))
->set(JsonRpcSetting::Cookie->value, boolval($form_state->getValue(JsonRpcSetting::Cookie->value)))
->set(JsonRpcSetting::JWT->value, boolval($form_state->getValue(JsonRpcSetting::JWT->value)))
->set(JsonRpcSetting::OAuth2->value, boolval($form_state->getValue(JsonRpcSetting::OAuth2->value)))
->save();
// Altering routes requires us to rebuild the routing table.
$this->routeBuilder->setRebuildNeeded();
parent::submitForm($form, $form_state);
}
}
......@@ -141,7 +141,7 @@ class Response implements CacheableDependencyInterface {
* True if it's an error response.
*/
public function isErrorResponse(): bool {
return isset($this->error);
return $this->error instanceof Error;
}
/**
......
<?php
declare(strict_types=1);
namespace Drupal\jsonrpc\Routing;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\jsonrpc\Enum\JsonRpcSetting;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Allows access to JSON-RPC routes based on the user-defined config.
*/
class JsonRpcRouteSubscriber extends RouteSubscriberBase {
/**
* The config defined by the user on the module settings page.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected ImmutableConfig $config;
/**
* Creates a new JsonRpcRouteSubscriber instance.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
*/
public function __construct(ConfigFactoryInterface $config_factory) {
$this->config = $config_factory->get('jsonrpc.settings');
}
/**
* {@inheritdoc}
*/
#[\Override]
protected function alterRoutes(RouteCollection $collection): void {
if (($route = $collection->get('jsonrpc.handler')) instanceof Route) {
$allowed_authorization_methods = [];
$basic_auth = boolval($this->config->get(JsonRpcSetting::BasicAuth->value));
if ($basic_auth) {
$allowed_authorization_methods[] = JsonRpcSetting::BasicAuth->value;
}
$cookie = boolval($this->config->get(JsonRpcSetting::Cookie->value));
if ($cookie) {
$allowed_authorization_methods[] = JsonRpcSetting::Cookie->value;
$route->setRequirement('_csrf_request_header_token', 'TRUE');
}
$jwt = boolval($this->config->get(JsonRpcSetting::JWT->value));
if ($jwt) {
$allowed_authorization_methods[] = JsonRpcSetting::JWT->value;
}
$oauth = boolval($this->config->get(JsonRpcSetting::OAuth2->value));
if ($oauth) {
$allowed_authorization_methods[] = JsonRpcSetting::OAuth2->value;
}
$route->setOption('_auth', $allowed_authorization_methods);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment