Skip to content
Snippets Groups Projects
Commit bd50136c authored by Joshua Sedler's avatar Joshua Sedler :cartwheel_tone2: Committed by Julian Pustkuchen
Browse files

Issue #2928649 by grevil, codeamatic, dakku, markaspot, shedow, anybody:...

Issue #2928649 by grevil, codeamatic, dakku, markaspot, shedow, anybody: namespace "api_key: causes access denied if a form has element also called 'api_key'
parent c21ffdb4
No related branches found
No related tags found
1 merge request!6Fix regression on Issue #2928649: namespace "api_key: causes access denied if a form has element also called 'api_key'
Pipeline #413792 passed with warnings
Showing
with 304 additions and 15 deletions
...@@ -25,6 +25,4 @@ include: ...@@ -25,6 +25,4 @@ include:
################ ################
# variables: # variables:
# OPT_IN_TEST_PREVIOUS_MAJOR: '1' # OPT_IN_TEST_PREVIOUS_MAJOR: '1'
# SKIP_ESLINT: '1'
# OPT_IN_TEST_NEXT_MAJOR: '1' # OPT_IN_TEST_NEXT_MAJOR: '1'
# _CURL_TEMPLATES_REF: 'main'
api_key_request_header_name: 'api_key'
api_key_post_parameter_name: ''
api_key_get_parameter_name: ''
# Schema for the configuration files of the Request API Key Authentication module.
services_api_key_auth.api_key.*: services_api_key_auth.api_key.*:
type: config_entity type: config_entity
label: 'API Key configuration' label: 'API Key configuration'
...@@ -15,3 +16,17 @@ services_api_key_auth.api_key.*: ...@@ -15,3 +16,17 @@ services_api_key_auth.api_key.*:
Label: 'API Key' Label: 'API Key'
user_uuid: user_uuid:
type: string type: string
services_api_key_auth.settings:
type: config_object
label: 'Request API Key Authentication settings'
mapping:
api_key_request_header_name:
type: string
label: 'API Key Request header name'
api_key_post_parameter_name:
type: string
label: 'API Key POST parameter name'
api_key_get_parameter_name:
type: string
label: 'API Key GET parameter name'
...@@ -2,14 +2,24 @@ ...@@ -2,14 +2,24 @@
/** /**
* @file * @file
* Install, update and uninstall functions for the services_api_key_auth module.
*/ */
declare(strict_types=1); declare(strict_types=1);
/** /**
* @file * Introduces new settings.
* Install, update and uninstall functions for the services_api_key_auth module. *
* Makes the request api key names configurable. And set their defaults to the
* old programmatically set values.
*/ */
function services_api_key_auth_update_10001() {
\Drupal::configFactory()->getEditable('services_api_key_auth.settings')
->set('api_key_request_header_name', 'apikey')
->set('api_key_post_parameter_name', 'api_key')
->set('api_key_get_parameter_name', 'api_key')
->save();
}
/** /**
* Service deprecation. * Service deprecation.
...@@ -17,6 +27,6 @@ declare(strict_types=1); ...@@ -17,6 +27,6 @@ declare(strict_types=1);
* The "authentication.api_key_auth" can now be accessed under the name * The "authentication.api_key_auth" can now be accessed under the name
* "services_api_key_auth.authentication.api_key_auth". * "services_api_key_auth.authentication.api_key_auth".
*/ */
function services_api_key_auth_update_10001() { function services_api_key_auth_update_10002() {
\Drupal::service('kernel')->rebuildContainer(); \Drupal::service('kernel')->rebuildContainer();
} }
...@@ -5,3 +5,10 @@ entity.api_key.collection: ...@@ -5,3 +5,10 @@ entity.api_key.collection:
description: 'Configure API Key registration for users' description: 'Configure API Key registration for users'
parent: system.admin_config_services parent: system.admin_config_services
weight: 99 weight: 99
services_api_key_auth.api_key_auth_settings:
title: Request API Key Authentication settings
description: The request api key authentication settings
parent: system.admin_config_system
route_name: system.admin_config_services
weight: 10
...@@ -32,3 +32,11 @@ entity.api_key.delete_form: ...@@ -32,3 +32,11 @@ entity.api_key.delete_form:
parameters: parameters:
api_key: api_key:
with_config_overrides: TRUE with_config_overrides: TRUE
services_api_key_auth.api_key_auth_settings:
path: '/admin/config/services/api-key-auth-settings'
defaults:
_title: 'Request API Key Authentication settings'
_form: 'Drupal\services_api_key_auth\Form\ApiKeyAuthSettingsForm'
requirements:
_permission: 'administer site configuration'
...@@ -126,25 +126,39 @@ class ApiKeyAuth implements AuthenticationProviderInterface { ...@@ -126,25 +126,39 @@ class ApiKeyAuth implements AuthenticationProviderInterface {
* True if api key is present * True if api key is present
*/ */
public function getKey(Request $request) { public function getKey(Request $request) {
// Exempt edit/delete form route. // Exempt this module's api key entitiy edit/delete form route:
$route_name = $this->currentRouteMatch->getRouteName(); $route_name = $this->currentRouteMatch->getRouteName();
if (str_contains($route_name ?? '', 'entity.api_key')) { if (str_contains($route_name ?? '', 'entity.api_key')) {
return FALSE; return FALSE;
} }
$form_api_key = $request->request->get('api_key'); $settings = $this->configFactory->get('services_api_key_auth.settings');
if (!empty($form_api_key)) {
return $form_api_key; // Check for the api key inside the request header ($_SERVER), if a server
// api key name is defined:
if ($serverApiKeyName = $settings->get('api_key_request_header_name')) {
$header_api_key = $request->headers->get($serverApiKeyName);
if (!empty($header_api_key)) {
return $header_api_key;
}
} }
$query_api_key = $request->query->get('api_key'); // Check for the api key inside the request body parameters ($_POST), if a
if (!empty($query_api_key)) { // post api key name is defined:
return $query_api_key; if ($postApiKeyName = $settings->get('api_key_post_parameter_name')) {
$form_api_key = $request->request->get($postApiKeyName);
if (!empty($form_api_key)) {
return $form_api_key;
}
} }
$header_api_key = $request->headers->get('apikey'); // Check for the api key inside the request query parameters ($_GET), if a
if (!empty($header_api_key)) { // query api key name is defined:
return $header_api_key; if ($queryApiKeyName = $settings->get('api_key_get_parameter_name')) {
$query_api_key = $request->query->get($queryApiKeyName);
if (!empty($query_api_key)) {
return $query_api_key;
}
} }
return FALSE; return FALSE;
} }
......
<?php
declare(strict_types=1);
namespace Drupal\services_api_key_auth\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure Request API Key Authentication settings for this site.
*/
final class ApiKeyAuthSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'services_api_key_auth_api_key_auth_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames(): array {
return ['services_api_key_auth.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('services_api_key_auth.settings');
$form['api_key_request_header_name'] = [
'#type' => 'textfield',
'#title' => $this->t('API Key Request header name'),
'#default_value' => $config->get('api_key_request_header_name'),
'#description' => $this->t('The name of the API key in the request header parameters ($_SERVER). Leave empty, to disable server API key authentication. NOTE, that the value of this key will be checked on EVERY request for this site.'),
];
$form['api_key_post_parameter_name'] = [
'#type' => 'textfield',
'#title' => $this->t('API Key POST parameter name'),
'#default_value' => $config->get('api_key_post_parameter_name'),
'#description' => $this->t('The name of the API key in the request body parameters ($_POST). Leave empty, to disable request post API key authentication. NOTE, that the value of this key will be checked on EVERY request for this site.'),
];
$form['api_key_get_parameter_name'] = [
'#type' => 'textfield',
'#title' => $this->t('API Key GET parameter name'),
'#default_value' => $config->get('api_key_get_parameter_name'),
'#description' => $this->t('The name of the API key in the request query parameters ($_GET). Leave empty, to disable request query API key authentication. NOTE, that the value of this key will be checked on EVERY request for this site.'),
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('services_api_key_auth.settings')
->set('api_key_request_header_name', $form_state->getValue('api_key_request_header_name'))
->set('api_key_post_parameter_name', $form_state->getValue('api_key_post_parameter_name'))
->set('api_key_get_parameter_name', $form_state->getValue('api_key_get_parameter_name'))
->save();
parent::submitForm($form, $form_state);
}
}
# Schema for the configuration files of the Request API Key Authentication Form Test module.
saka_form_test.settings:
type: config_object
label: 'Request API Key Authentication Form Test settings'
mapping:
example:
type: string
label: 'Example'
name: 'Request API Key Authentication Form Test'
description: 'A test module which tests, whether "api_key" is a valid form element name.'
type: module
package: Custom
core_version_requirement: ^10.3 || ^11
dependencies:
- services_api_key_auth:services_api_key_auth
saka_form_test.test_settings:
path: '/admin/config/system/test-settings'
defaults:
_title: 'Test settings'
_form: 'Drupal\saka_form_test\Form\TestSettingsForm'
requirements:
_permission: 'administer site configuration'
<?php
declare(strict_types=1);
namespace Drupal\saka_form_test\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure Request API Key Authentication Form Test settings for this site.
*/
final class TestSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'saka_form_test_test_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames(): array {
return ['saka_form_test.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['api_key'] = [
'#type' => 'textfield',
'#title' => $this->t('Api Key'),
'#description' => $this->t('This form element should be submittable without any problem.'),
'#required' => TRUE,
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$this->messenger()->addMessage($this->t('Form submitted successfully.'));
}
}
<?php
namespace Drupal\Tests\services_api_key_auth\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* This class provides methods specifically for testing something.
*
* @group services_api_key_auth
*/
class ApiKeyFormTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'services_api_key_auth',
'saka_form_test',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Login as root user:
$this->drupalLogin($this->rootUser);
}
/**
* Tests if the module installation, won't break the site.
*
* @see https://www.drupal.org/project/services_api_key_auth/issues/2928649
*/
public function testApiKeyFormElementCausingAccessDenied() {
// Set the api_key_post_parameter_name to "api_key":
$this->config('services_api_key_auth.settings')
->set('api_key_post_parameter_name', 'api_key')
->save();
$session = $this->assertSession();
$page = $this->getSession()->getPage();
// Go to the test module settings page:
$this->drupalGet('/admin/config/system/test-settings');
$session->statusCodeEquals(200);
$page->fillField('api_key', 'test-api-key');
$page->pressButton('Save configuration');
// Since "api_key" exists as form element and has a value, it will be one
// of the post parameters and therefore collide, with our
// "api_key_post_parameter_name" key name. This will cause an access denied error:
$session->statusCodeEquals(403);
// Remove the "api_key_post_parameter_name" value, so that authentication through
// post parameters is disabled:
$this->config('services_api_key_auth.settings')
->set('api_key_post_parameter_name', '')
->save();
// Now we should be able to save the form:
$this->drupalGet('/admin/config/system/test-settings');
$session->statusCodeEquals(200);
$page->fillField('api_key', 'test-api-key');
$page->pressButton('Save configuration');
$session->statusCodeEquals(200);
$session->pageTextContains('Form submitted successfully.');
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment