View source
<?php
namespace Drupal\simplesamlphp_auth\Service;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\externalauth\ExternalAuthInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\user\UserInterface;
use Psr\Log\LoggerInterface;
class SimplesamlphpDrupalAuth {
use StringTranslationTrait;
protected $simplesamlAuth;
protected $config;
protected $entityTypeManager;
protected $logger;
protected $externalauth;
protected $currentUser;
protected $messenger;
protected $moduleHandler;
public function __construct(SimplesamlphpAuthManager $simplesaml_auth, ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger, ExternalAuthInterface $externalauth, AccountInterface $account, MessengerInterface $messenger, ModuleHandlerInterface $module_handler) {
$this->simplesamlAuth = $simplesaml_auth;
$this->config = $config_factory
->get('simplesamlphp_auth.settings');
$this->entityTypeManager = $entity_type_manager;
$this->logger = $logger;
$this->externalauth = $externalauth;
$this->currentUser = $account;
$this->messenger = $messenger;
$this->moduleHandler = $module_handler;
}
public function externalLoginRegister($authname) {
$account = $this->externalauth
->login($authname, 'simplesamlphp_auth');
if (!$account) {
$account = $this
->externalRegister($authname);
}
if ($account) {
if ($this->config
->get('role.eval_every_time')) {
$this
->roleMatchSync($account);
}
}
return $account;
}
public function externalRegister($authname) {
$account = FALSE;
$existing_user = $this->entityTypeManager
->getStorage('user')
->loadByProperties([
'name' => $authname,
]);
$existing_user = $existing_user ? reset($existing_user) : FALSE;
if ($existing_user) {
if ($this->config
->get('autoenablesaml')) {
if ($this->config
->get('debug')) {
$this->logger
->debug('Linking authname %authname to existing Drupal user with ID %id because "Automatically enable SAML authentication for existing users upon successful login" setting is activated.', [
'%authname' => $authname,
'%id' => $existing_user
->id(),
]);
}
$this->externalauth
->linkExistingAccount($authname, 'simplesamlphp_auth', $existing_user);
$account = $existing_user;
}
else {
if ($this->config
->get('debug')) {
$this->logger
->debug('A local Drupal user with username %authname already exists. Aborting the creation of a SAML-enabled Drupal user.', [
'%authname' => $authname,
]);
}
$this->messenger
->addMessage($this
->t('We are sorry, your user account is not SAML enabled.'), 'status');
$this->simplesamlAuth
->logout(base_path());
return FALSE;
}
}
else {
if ($this->config
->get('autoenablesaml')) {
$attributes = $this->simplesamlAuth
->getAttributes();
foreach ($this->moduleHandler
->getImplementations('simplesamlphp_auth_existing_user') as $module) {
$return_value = $this->moduleHandler
->invoke($module, 'simplesamlphp_auth_existing_user', [
$attributes,
]);
if ($return_value instanceof UserInterface) {
$account = $return_value;
if ($this->config
->get('debug')) {
$this->logger
->debug('Linking authname %authname to existing Drupal user with ID %id because "Automatically enable SAML authentication for existing users upon successful login" setting is activated.', [
'%authname' => $authname,
'%id' => $account
->id(),
]);
}
$this->externalauth
->linkExistingAccount($authname, 'simplesamlphp_auth', $account);
}
}
}
if (!$this->config
->get('register_users')) {
$this->messenger
->addMessage($this
->t('We are sorry. While you have successfully authenticated, you are not yet entitled to access this site. Please ask the site administrator to provision access for you.'), 'status');
$this->simplesamlAuth
->logout(base_path());
return FALSE;
}
}
if (!$account) {
try {
$account = $this->externalauth
->register($authname, 'simplesamlphp_auth');
} catch (\Exception $ex) {
watchdog_exception('simplesamlphp_auth', $ex);
$this->messenger
->addMessage($this
->t('Error registering user: An account with this username already exists.'), 'error');
}
}
if ($account) {
$this
->synchronizeUserAttributes($account, TRUE);
return $this->externalauth
->userLoginFinalize($account, $authname, 'simplesamlphp_auth');
}
}
public function synchronizeUserAttributes(AccountInterface $account, $force = FALSE) {
$sync_mail = $force || $this->config
->get('sync.mail');
$sync_user_name = $force || $this->config
->get('sync.user_name');
if ($sync_user_name) {
$name = $this->simplesamlAuth
->getDefaultName();
if ($name) {
$existing = FALSE;
$account_search = $this->entityTypeManager
->getStorage('user')
->loadByProperties([
'name' => $name,
]);
if ($existing_account = reset($account_search)) {
if ($account
->id() != $existing_account
->id()) {
$existing = TRUE;
$logger_params = [
'%username' => $name,
'%new_uid' => $this->currentUser
->id(),
'%existing_uid' => $existing_account
->id(),
];
$this->logger
->critical("Error on synchronizing name attribute for uid %new_uid: an account with the username %username and uid %existing_uid already exists.", $logger_params);
$this->messenger
->addMessage($this
->t('Error synchronizing username: an account with this username already exists.'), 'error');
}
}
if (!$existing) {
$account
->setUsername($name);
}
}
else {
$this->logger
->critical("Error on synchronizing name attribute: no username available for Drupal user %id.", [
'%id' => $account
->id(),
]);
$this->messenger
->addMessage($this
->t('Error synchronizing username: no username is provided by SAML.'), 'error');
}
}
if ($sync_mail && $this->config
->get('mail_attr')) {
$mail = $this->simplesamlAuth
->getDefaultEmail();
if ($mail) {
$account
->setEmail($mail);
}
else {
$this->logger
->critical("Error on synchronizing mail attribute: no email address available for Drupal user %id.", [
'%id' => $account
->id(),
]);
$this->messenger
->addMessage($this
->t('Error synchronizing mail: no email address is provided by SAML.'), 'error');
}
}
if ($sync_mail || $sync_user_name) {
$account
->save();
}
}
public function roleMatchSync(UserInterface $account) {
$matching_roles = $this
->getMatchingRoles();
$current_roles = $account
->getRoles(TRUE);
$account_updated = FALSE;
foreach (array_diff($current_roles, $matching_roles) as $role_id) {
if ($this->config
->get('debug')) {
$this->logger
->debug('Removing role %role from user %name', [
'%role' => $role_id,
'%name' => $account
->getAccountName(),
]);
}
$account
->removeRole($role_id);
$account_updated = TRUE;
}
foreach (array_diff($matching_roles, $current_roles) as $role_id) {
if ($this->config
->get('debug')) {
$this->logger
->debug('Adding role %role to user %name', [
'%role' => $role_id,
'%name' => $account
->getAccountName(),
]);
}
$account
->addRole($role_id);
$account_updated = TRUE;
}
if ($account_updated) {
$account
->save();
}
}
public function getMatchingRoles() {
$roles = [];
if ($rolemap = $this->config
->get('role.population')) {
foreach (explode('|', $rolemap) as $rolerule) {
list($role_id, $role_eval) = explode(':', $rolerule, 2);
foreach (explode(';', $role_eval) as $role_eval_part) {
if ($this
->evalRoleRule($role_eval_part)) {
$roles[$role_id] = $role_id;
}
}
}
}
$attributes = $this->simplesamlAuth
->getAttributes();
$this->moduleHandler
->alter('simplesamlphp_auth_user_roles', $roles, $attributes);
return $roles;
}
protected function evalRoleRule($role_eval_part) {
list($key, $op, $value) = explode(',', $role_eval_part);
$attributes = $this->simplesamlAuth
->getAttributes();
if ($this->config
->get('debug')) {
$this->logger
->debug('Evaluate rule (key=%key,operator=%op,value=%val', [
'%key' => $key,
'%op' => $op,
'%val' => $value,
]);
}
if (!array_key_exists($key, $attributes)) {
return FALSE;
}
$attribute = $attributes[$key];
switch ($op) {
case '=':
return in_array($value, $attribute);
case '@=':
list($before, $after) = explode('@', array_shift($attribute));
return $after == $value;
case '~=':
return array_filter($attribute, function ($subattr) use ($value) {
return strpos($subattr, $value) !== FALSE;
});
}
}
}