wayf-1.0.x-dev/wayf.module
wayf.module
<?php
use Drupal\Core\Session\AccountInterface;
use Drupal\wayf\WAYF;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Implements hook_cron().
*/
function wayf_cron() {
// Execute every 24 hours.
$interval = 24*60*60;
$last_check = \Drupal::state()->get('wayf.last_fetch', 0);
$time = \Drupal::time()->getRequestTime();
if ($time - $last_check > $interval) {
wayf__organizations_list();
\Drupal::state()->set('wayf.last_fetch', $time);
}
}
/**
* Implements hook_theme().
*/
function wayf_theme($existing, $type, $theme, $path) {
return [
'wayf_login' => [
'variables' => [
'icon' => '',
'login_url' => '/wayf/consume',
],
'template' => 'wayf-login',
],
];
}
/**
* @inheritdoc
*/
function wayf_user_cancel($edit, $account, $method) {
if ('user_cancel_delete' === $method) {
\Drupal::database()->delete('wayf')
->condition('uid', $account->id())
->execute();
}
}
/**
* Generate metadata based on configuration.
*
* @return string
* XML string with the metadata.
*/
function wayf_generate_metadata() {
$config = \Drupal::config('wayf.settings');
// Set library configuration.
$config = [
'cert' => $config->get('sp_cert') ? str_replace("\r\n", '', $config->get('sp_cert')) : '',
'asc' => $config->get('sp_endpoint'),
'logout_redirect' => $config->get('sp_logout_endpoint'),
'entityid' => $config->get('sp_entityid'),
'contact' => [
'name' => $config->get('sp_contact_name'),
'mail' => $config->get('sp_contact_mail'),
],
'organization' => [
'language' => $config->get('sp_organizations_name_language'),
'name' => $config->get('sp_organizations_name'),
'displayname' => $config->get('sp_organizations_displayname'),
'url' => $config->get('sp_organizations_url'),
],
];
return (new WAYF\SPorto($config))->getMetadata();
}
/**
* Implements hook_wayf_create_user();
*/
function wayf_wayf_create_user($attributes) {
// eduPersonTargetedID is a persistent, non-reassigned, privacy-preserving
// identifier designed to provide a service provider with a unique
// identifier for a logged in person while preserving the person's privacy.
$edu_person_targeted_id = $attributes['eduPersonTargetedID'][0];
// The following code is based on user_external_login_register that does
// not allow the external identifier to be different from drupal username.
$account = wayf_load_account($edu_person_targeted_id);
if (!$account) {
/** @var AccountInterface $account */
$account = wayf_create_account($edu_person_targeted_id, $attributes);
// Save eduPersonTargetedID -> uid mapping.
$record = [
'edu_person_targeted_id' => $edu_person_targeted_id,
'uid' => $account->id(),
'organization' => $attributes['schacHomeOrganization'][0],
];
\Drupal::database()->insert('wayf')
->fields($record)
->execute();
}
if ($account) {
user_login_finalize($account);
} else {
\Drupal::messenger()->addError(t('Login failed'));
\Drupal::logger('wayf')->info("Login failed : %name.", ['%name' => $edu_person_targeted_id]);
}
}
/**
* Load user account.
*
* @param $edu_person_targeted_id
*/
function wayf_load_account($edu_person_targeted_id) {
$uid = \Drupal::database()->query('SELECT uid FROM {wayf} WHERE edu_person_targeted_id = :eptid', [
':eptid' => $edu_person_targeted_id,
])->fetchField();
if ($uid) {
return Drupal\user\Entity\User::load($uid);
}
return FALSE;
}
/**
* Implements hook_entity_delete().
*
* Removes the user entry in the WAYF table upon deletion.
*/
function wayf_entity_delete(\Drupal\Core\Entity\EntityInterface $entity) {
if ('user' === $entity->getEntityType()->id()) {
$conn = \Drupal::database();
$conn->delete('wayf')
->condition('uid', $entity->id())
->execute();
}
}
/**
* Create new user account.
*
* @param $edu_person_targeted_id
* @param $attributes
*
* @return RedirectResponse|AccountInterface
*/
function wayf_create_account($edu_person_targeted_id, $attributes) {
// Create a user entity.
$userinfo = [];
$userinfo['name'] = $edu_person_targeted_id;
$userinfo['mail'] = $attributes['mail'][0];
$userinfo['pass'] = \Drupal::service('password_generator')->generate(32);
$userinfo['status'] = 1;
try {
$account = Drupal\user\Entity\User::create($userinfo);
wayf__map_fields($attributes, $account);
$account->enforceIsNew();
$account->save();
} catch (\Exception $e) {
\Drupal::messenger()->addError(t('Error saving user account.'));
\Drupal::logger('wayf')->error('Error saving user account : %name.', [
'%name' => $edu_person_targeted_id,
]
);
return new RedirectResponse('<front>');
}
\Drupal::logger('wayf')->info('New account created : %name.', [
'%name' => $edu_person_targeted_id,
]
);
return $account;
}
/**
* Implements hook_user_logout().
*
* Ensures that the WAYF logout function is called on user logout.
*/
function wayf_user_logout() {
// Load configuration.
$config = \Drupal::config('wayf.settings');
// Set library configuration.
$sportoConfig = [
'idp_certificate' => $config->get('idp_certificate'),
'sso' => $config->get('idp_sso'),
'slo' => $config->get('idp_slo'),
'private_key' => $config->get('sp_key'),
'asc' => $config->get('sp_endpoint'),
'entityid' => $config->get('sp_entityid'),
];
// Check if the user maybe logged into WAYF.
$sporto = new WAYF\SPorto($sportoConfig);
if ($sporto->isLoggedIn()) {
// Give other a change to clean up.
\Drupal::moduleHandler()->invokeAll('wayf_pre_logout');
// Send logout message.
$sporto->logout();
}
}
/**
* Check if user is mapped in the WAYF table.
*
* @return bool
*/
function wayf_current_user_wayf_mapped(): bool {
$account = \Drupal::currentUser();
$status = \Drupal::database()->query('SELECT count(*) FROM {wayf} WHERE uid = :uid', [
':uid' => $account->id()
])->fetchField();
return $status == 1;
}
/**
* Map fields from released attributes to user fields.
*/
function wayf__map_fields($attributes, $account) {
$mapping = \Drupal::config('wayf.settings')->get('mapping');
foreach ($mapping as $field_name => $source) {
if (!empty($source) && !empty($attributes[$source][0])) {
$account->$field_name = $attributes[$source][0];
}
}
}
function wayf__icons() {
return [
'UK_01.png',
'UK_02.png',
'UK_03.png',
'UK_04.png',
'UK_05.png',
'UK_01G.png',
'UK_02G.png',
'UK_03G.png',
'UK_04G.png',
'UK_05G.png',
'wayf_logo.png',
];
}
/**
* @param string $icon
* The filename of the icon to return size of.
*
* @return object|null
*/
function wayf__icon_size($icon): object|null {
switch ($icon) {
case 'UK_01G.png':
return (object) ['width' => 83, 'height' => 34];
case 'UK_01.png':
return (object) ['width' => 79, 'height' => 34];
case 'UK_02.png':
case 'UK_02G.png':
return (object) ['width' => 79, 'height' => 41];
case 'UK_03G.png':
return (object) ['width' => 93, 'height' => 25];
case 'UK_03.png':
return (object) ['width' => 94, 'height' => 26];
case 'UK_04G.png':
return (object) ['width' => 57, 'height' => 25];
case 'UK_04.png':
return (object) ['width' => 58, 'height' => 24];
case 'UK_05.png':
case 'UK_05G.png':
return (object) ['width' => 71, 'height' => 31];
case 'wayf_logo.png':
return (object) ['width' => 125, 'height' => 65];
default:
return null;
}
}
/**
* {@inheritdoc}
*/
function wayf_form_user_login_form_alter(&$form, &$form_state) {
$config = \Drupal::config('wayf.settings');
if ($config->get('alter_login_form')) {
$icon = \Drupal::service('extension.path.resolver')->getPath('module', 'wayf') . '/icons/' . $config->get('icon');
$icon_size = wayf__icon_size($config->get('icon'));
$uri = \Drupal::service('file_url_generator')->generateAbsoluteString($icon);
$form['wayf_login'] = [
'#weight' => 1000,
'#theme' => 'wayf_login',
'#icon' => [
'uri'=> $uri,
'size' => $icon_size
],
'#login_url' => '/wayf/consume',
];
}
}
/**
* Implements hook_form_alter().
*
* Remove the current password field from the user_profile_form form (user/%/edit).
*/
function wayf_form_user_form_alter(&$form, &$form_state) {
$config = \Drupal::config('wayf.settings');
if (wayf_current_user_wayf_mapped()) {
if ($config->get('validate_current_pass_disable')) {
// searches the #validate array for the current_pass validation function, and removes it
$key = array_search('user_validate_current_pass', $form['#validate']);
if (FALSE !== $key) {
unset($form['#validate'][$key]);
}
// hide the current password fields
$form['account']['current_pass_required_value']['#access'] = FALSE;
$form['account']['current_pass']['#access'] = FALSE;
}
if ($config->get('pass_disable')) {
$form['account']['pass']['#access'] = FALSE;
}
}
}
/**
* @return object
* Object with the service providers certificate og location.
*/
function wayf__get_ipd_metadata() {
$metadata_url = 'https://metadata.wayf.dk/wayf-metadata.xml';
// Set default information.
$info = (object) [
'cert' => '',
'sso' => '',
'slo' => '',
];
$metadata = @file_get_contents($metadata_url);
if (!$metadata) {
/** @var \Drupal\Core\Messenger\Messenger $messenger */
$messenger = \Drupal::service('messenger');
$messenger->addError(t('An error occurred, WAYF metadata service not available'));
}
else {
$xml = simplexml_load_string($metadata);
$xml->registerXPathNamespace('md', 'urn:oasis:names:tc:SAML:2.0:metadata');
$xml->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
$idpssodescriptor = '//md:EntityDescriptor/md:IDPSSODescriptor';
$binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect';
// Get SignOn and logout urls.
$sso = $xml->xpath("$idpssodescriptor/md:SingleSignOnService[@Binding='$binding']/@Location");
$slo = $xml->xpath("$idpssodescriptor/md:SingleLogoutService[@Binding='$binding']/@Location");
// Get certificate data.
$cert = $xml->xpath("$idpssodescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate");
// Set information form the meta-date.
$info = (object) [
'cert' => (string) $cert[0],
'sso' => (string) $sso[0]['Location'],
'slo' => (string) $slo[0]['Location'],
];
}
return $info;
}
/**
* Import organizations from the WAYF service.
*/
function wayf__organizations_list() {
// Set default values for config which require dynamic values.
$config = \Drupal::configFactory()->getEditable('wayf.settings');
// Get list from WAYF.
$feed_url = $config->get('sp_organizations_list_url');
$content = file_get_contents($feed_url);
// De-code the data and the test end-point.
$data = json_decode($content, TRUE);
$data['https://testidp.wayf.dk/module.php/core/loginuserpass.php'] = [
'da' => 'WAYF test-institution (IDP)',
'en' => 'WAYF test-institution (IDP)',
'schacHomeOrganization' => 'testidp.wayf.dk',
];
// Re-encode to json as config don't allow dot's in any depth in keys.
$config->set('sp_organizations_list', json_encode($data));
$config->save();
}
