oidc-1.0.0-alpha2/src/Form/RealmsConfigForm.php
src/Form/RealmsConfigForm.php
<?php
namespace Drupal\oidc\Form;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\Core\Link;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Url;
use Drupal\externalauth\AuthmapInterface;
use Drupal\oidc\JsonHttp\JsonHttpClientInterface;
use Drupal\oidc\OpenidConnectRealm\OpenidConnectRealmConfigurableInterface;
use Drupal\oidc\OpenidConnectRealm\OpenidConnectRealmManager;
use Drupal\oidc\Plugin\OpenidConnectRealm\GenericOpenidConnectRealm;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The realms configuration form.
*/
class RealmsConfigForm extends ConfigFormBase {
/**
* The OpenID Connect realm manager.
*
* @var \Drupal\oidc\OpenidConnectRealm\OpenidConnectRealmManager
*/
protected $realmManager;
/**
* The authentication mapping service.
*
* @var \Drupal\externalauth\AuthmapInterface
*/
protected $authmap;
/**
* The UUID service.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected $uuid;
/**
* The key-value storage factory.
*
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
*/
protected $keyValueFactory;
/**
* The JSON HTTP client.
*
* @var \Drupal\oidc\JsonHttp\JsonHttpClientInterface
*/
protected $jsonHttpClient;
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* The logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Class constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory service.
* @param Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
* The typed config manager.
* @param \Drupal\oidc\OpenidConnectRealm\OpenidConnectRealmManager $realm_manager
* The OpenID Connect realm manager.
* @param \Drupal\externalauth\AuthmapInterface $authmap
* The authentication mapping service.
* @param \Drupal\Component\Uuid\UuidInterface $uuid
* The UUID service.
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
* The key-value storage factory.
* @param \Drupal\oidc\JsonHttp\JsonHttpClientInterface $json_http_client
* The JSON HTTP client.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Psr\Log\LoggerInterface $logger
* The logger.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config_manager, OpenidConnectRealmManager $realm_manager, AuthmapInterface $authmap, UuidInterface $uuid, KeyValueFactoryInterface $key_value_factory, JsonHttpClientInterface $json_http_client, TimeInterface $time, LoggerInterface $logger, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($config_factory, $typed_config_manager);
$this->realmManager = $realm_manager;
$this->authmap = $authmap;
$this->uuid = $uuid;
$this->keyValueFactory = $key_value_factory;
$this->jsonHttpClient = $json_http_client;
$this->time = $time;
$this->logger = $logger;
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('config.typed'),
$container->get('plugin.manager.openid_connect_realm'),
$container->get('externalauth.authmap'),
$container->get('uuid'),
$container->get('keyvalue'),
$container->get('oidc.json_http_client'),
$container->get('datetime.time'),
$container->get('logger.channel.oidc'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['oidc.settings'];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'oidc_realms_config_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['redirect_urls'] = [
'#type' => 'details',
'#title' => $this->t('Redirect URLs'),
'#description' => $this->t('These redirect URLs should be accepted by the OpenID Connect authentication server.'),
'#open' => TRUE,
];
$url = Url::fromRoute('oidc.openid_connect.login_redirect')->setAbsolute();
$form['redirect_urls']['login'] = [
'#type' => 'item',
'#title' => $this->t('Login redirect URL'),
'#markup' => Link::fromTextAndUrl($url->toString(), $url)->toString(),
];
$url = Url::fromRoute('oidc.openid_connect.logout_redirect')->setAbsolute();
$form['redirect_urls']['logout'] = [
'#type' => 'item',
'#title' => $this->t('Logout redirect URL'),
'#markup' => Link::fromTextAndUrl($url->toString(), $url)->toString(),
];
// Realms.
$form['realms'] = [
'#type' => 'container',
'#tree' => TRUE,
'#id' => 'openid-connect-realms',
];
if (!$form_state->has('generic_realms')) {
// Store the generic realms.
if ($generic_realms = $this->config('oidc.settings')->get('generic_realms')) {
$generic_realms = array_combine($generic_realms, $generic_realms);
}
$form_state->set('generic_realms', $generic_realms ?? []);
// Store all realms.
if ($realms = $this->realmManager->getConfigurable()) {
$realms = array_combine($realms, $realms);
}
$form_state->set('realms', $realms);
}
// Add the realm specific forms.
foreach ($this->configurablePlugins($form_state) as $plugin_id => $plugin) {
$form['realms'][$plugin_id] = [
'#type' => 'details',
'#title' => $plugin->getPluginDefinition()['name'],
'#open' => TRUE,
];
$url = Url::fromRoute('oidc.openid_connect.login', [
'realm' => $plugin_id,
])->setAbsolute();
$form['realms'][$plugin_id]['login_url'] = [
'#type' => 'item',
'#title' => $this->t('Login URL'),
'#markup' => Link::fromTextAndUrl($url->toString(), $url)->toString(),
];
$form['realms'][$plugin_id]['settings'] = [
// Added for the convenience of plugins.
'#parents' => ['realms', $plugin_id, 'settings'],
];
$subform_state = SubformState::createForSubform($form['realms'][$plugin_id]['settings'], $form, $form_state);
$form['realms'][$plugin_id]['settings'] = $plugin->buildConfigurationForm($form['realms'][$plugin_id]['settings'], $subform_state);
// Allow deleting a generic realm.
if ($plugin instanceof GenericOpenidConnectRealm) {
$html_id = Html::getId('openid-connect-realms--' . $plugin_id);
$form['realms'][$plugin_id] += [
'#prefix' => '<div id="' . $html_id . '">',
'#suffix' => '</div>',
];
$form['realms'][$plugin_id]['actions'] = [
'#type' => 'actions',
];
$form['realms'][$plugin_id]['actions']['delete'] = [
'#type' => 'submit',
'#value' => $this->t('Delete'),
'#name' => 'delete_' . $html_id,
'#parents' => [],
'#limit_validation_errors' => [],
'#submit' => ['::deleteGenericRealmSubmit'],
'#ajax' => [
'wrapper' => $html_id,
'callback' => '::deleteGenericRealmAjax',
],
'#plugin_id' => $plugin_id,
];
}
}
// Actions.
$form['actions'] = [
'#type' => 'actions',
];
$form['actions']['add'] = [
'#type' => 'submit',
'#value' => $this->t('Add generic realm'),
'#limit_validation_errors' => [],
'#submit' => ['::addGenericRealmSubmit'],
'#button_type' => 'secondary',
'#ajax' => [
'wrapper' => 'openid-connect-realms',
'callback' => '::addGenericRealmAjax',
'method' => 'append',
],
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
foreach ($this->configurablePlugins($form_state) as $plugin_id => $plugin) {
$subform_state = SubformState::createForSubform($form['realms'][$plugin_id]['settings'], $form, $form_state);
$plugin->validateConfigurationForm($form['realms'][$plugin_id]['settings'], $subform_state);
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$generic_realms = $this->config('oidc.settings')->get('generic_realms');
// Clear the storage and delete the authmap entries of deleted generic realms.
if ($generic_realms) {
$deleted_generic_realms = array_diff(
$generic_realms,
$form_state->get('generic_realms')
);
foreach ($deleted_generic_realms as $derivative_id) {
$plugin_id = 'generic' . PluginBase::DERIVATIVE_SEPARATOR;
$plugin_id .= $derivative_id;
$this->realmManager
->loadInstance($plugin_id)
->clearStorage();
$this->realmManager->deleteInstance($plugin_id);
$this->authmap->deleteProvider('oidc:' . $plugin_id);
}
}
// Save the generic realms.
if ($generic_realms = $form_state->get('generic_realms')) {
$generic_realms = array_values($generic_realms);
}
$this->config('oidc.settings')
->set('generic_realms', $generic_realms)
->save();
// Save the realm configurations.
foreach ($this->configurablePlugins($form_state) as $plugin_id => $plugin) {
$subform_state = SubformState::createForSubform($form['realms'][$plugin_id]['settings'], $form, $form_state);
$plugin->submitConfigurationForm($form['realms'][$plugin_id]['settings'], $subform_state);
$this->realmManager->saveInstance($plugin);
}
$this->realmManager->clearCachedDefinitions();
parent::submitForm($form, $form_state);
}
/**
* Form submit handler; Add a new generic realm.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function addGenericRealmSubmit(array &$form, FormStateInterface $form_state) {
// Add a generic realms entry.
$derivative_id = str_replace('-', '_', $this->uuid->generate());
$generic_realms = &$form_state->get('generic_realms');
$generic_realms[$derivative_id] = $derivative_id;
// Add a realms entry.
$plugin_id = 'generic' . PluginBase::DERIVATIVE_SEPARATOR . $derivative_id;
$realms = &$form_state->get('realms');
$realms[$plugin_id] = $plugin_id;
$form_state->setRebuild();
}
/**
* Ajax callback; Returns the newly added realm element.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* Form element for the newly added realm.
*/
public function addGenericRealmAjax(array $form, FormStateInterface $form_state) {
$plugin_id = $form_state->get('realms');
$plugin_id = end($plugin_id);
return $form['realms'][$plugin_id];
}
/**
* Form submit handler; Delete a generic realm.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function deleteGenericRealmSubmit(array &$form, FormStateInterface $form_state) {
// Delete the realms entry.
$plugin_id = $form_state->getTriggeringElement()['#plugin_id'];
$realms = &$form_state->get('realms');
unset($realms[$plugin_id]);
// Delete the generic realms entry.
$derivative_id = explode(PluginBase::DERIVATIVE_SEPARATOR, $plugin_id)[1];
$generic_realms = &$form_state->get('generic_realms');
unset($generic_realms[$derivative_id]);
$form_state->setRebuild();
}
/**
* Ajax callback; Returns the ajax response for when a generic realm was deleted.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* Ajax response for when a realm was deleted.
*/
public function deleteGenericRealmAjax(array $form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand(NULL, ''));
return $response;
}
/**
* Get the configurable realm plugins.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\oidc\OpenidConnectRealm\OpenidConnectRealmConfigurableInterface[]
* The configurable realm plugins keyed by plugin ID.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function configurablePlugins(FormStateInterface $form_state) {
$plugins = [];
foreach ($form_state->get('realms') as $plugin_id) {
if ($this->realmManager->hasDefinition($plugin_id)) {
// Existing plugin, load the instance.
$plugin = $this->realmManager->loadInstance($plugin_id);
if ($plugin instanceof OpenidConnectRealmConfigurableInterface) {
$plugins[$plugin_id] = $plugin;
}
}
else {
// New (unsaved) generic realm.
$plugins[$plugin_id] = new GenericOpenidConnectRealm(
[],
$plugin_id,
[
'id' => $plugin_id,
'name' => $this->t('New realm'),
'class' => GenericOpenidConnectRealm::class,
'provider' => 'oidc',
],
$this->keyValueFactory,
$this->jsonHttpClient,
$this->time,
$this->logger,
$this->entityTypeManager
);
}
}
return $plugins;
}
}
