Commit ca7e11b1 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Configuration de la stratégie d'extraction d'un identifiant utile parmi les...

Configuration de la stratégie d'extraction d'un identifiant utile parmi les données d'authentification shibboleth (config 'shib_user_id_extractor').
parent abb31635
Pipeline #9705 passed with stage
in 17 seconds
......@@ -40,6 +40,8 @@ Première version officielle sous ZF3.
3.2.0
-----
- Configuration de la stratégie d'extraction d'un identifiant utile parmi les données d'authentification shibboleth
(config 'shib_user_id_extractor').
- Possibilité de connaître la source de l'authentification (db, ldap, cas, shib).
- Possibilité de stopper l'usurpation en cours pour revenir à l'identité d'origine.
- [FIX] Usurpation d'un compte local en BDD.
......@@ -187,6 +187,44 @@ $settings = [
'supannEtuId|supannEmpId',
],
*/
/**
* Configuration de la stratégie d'extraction d'un identifiant utile parmi les données d'authentification
* shibboleth.
* Ex: identifiant de l'usager au sein du référentiel établissement, transmis par l'IDP via le supannRefId.
*/
'shib_user_id_extractor' => [
/*
// domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr')
'unicaen.fr' => [
[
// nom du 1er attribut recherché
'name' => 'supannRefId', // ex: '{OCTOPUS:ID}1234;{ISO15693}044D1AZE7A5P80'
// pattern éventuel pour extraire la partie intéressante
'preg_match_pattern' => '|\{OCTOPUS:ID\}(\d+)|', // ex: permet d'extraire '1234'
],
[
// nom du 2e attribut recherché, si le 1er est introuvable
'name' => 'supannEmpId',
// pas de pattern donc valeur brute utilisée
'preg_match_pattern' => null,
],
[
// nom du 3e attribut recherché, si le 2e est introuvable
'name' => 'supannEtuId',
],
],
*/
// config de repli pour tous les autres domaines
'default' => [
[
'name' => 'supannEmpId',
],
[
'name' => 'supannEtuId',
],
],
],
],
/**
......
......@@ -115,6 +115,42 @@ return [
// 'givenName',
// 'supannEtuId|supannEmpId',
//],
/**
* Configuration de la stratégie d'extraction d'un identifiant utile parmi les données d'authentification
* shibboleth.
* Ex: identifiant de l'usager au sein du référentiel établissement, transmis par l'IDP via le supannRefId.
*/
'shib_user_id_extractor' => [
// domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr')
// 'unicaen.fr' => [
// [
// // nom du 1er attribut recherché
// 'name' => 'supannRefId', // ex: '{OCTOPUS:ID}1234;{ISO15693}044D1AZE7A5P80'
// // pattern éventuel pour extraire la partie intéressante
// 'preg_match_pattern' => '|\{OCTOPUS:ID\}(\d+)|', // ex: permet d'extraire '1234'
// ],
// [
// // nom du 2e attribut recherché
// 'name' => 'supannEmpId',
// // pas de pattern donc valeur brute utilisée
// 'preg_match_pattern' => null,
// ],
// [
// // nom du 3e attribut recherché
// 'name' => 'supannEtuId',
// ],
// ],
// config de repli pour tous les autres domaines
'default' => [
[
'name' => 'supannEmpId',
],
[
'name' => 'supannEtuId',
],
],
],
],
/**
......
......@@ -28,11 +28,11 @@ class LocalAdapterFactory
// db adapter
if (array_key_exists('db', $localConfig)) {
$this->attachSubAdapter($localAdapter, $container, $localConfig['db']);
$this->attachSubAdapter($localAdapter, $container, $localConfig['db'], 20); // en 1er
}
// ldap adapter
if (array_key_exists('ldap', $localConfig)) {
$this->attachSubAdapter($localAdapter, $container, $localConfig['ldap']);
$this->attachSubAdapter($localAdapter, $container, $localConfig['ldap'], 10); // en 2e
}
return $localAdapter;
......@@ -42,8 +42,9 @@ class LocalAdapterFactory
* @param LocalAdapter $localAdapter
* @param ContainerInterface $container
* @param array $config
* @param int $priority
*/
private function attachSubAdapter(LocalAdapter $localAdapter, ContainerInterface $container, array $config)
private function attachSubAdapter(LocalAdapter $localAdapter, ContainerInterface $container, array $config, int $priority)
{
$enabled = isset($config['enabled']) && $config['enabled'] === true;
if (! $enabled) {
......@@ -52,7 +53,7 @@ class LocalAdapterFactory
/** @var AbstractAdapter $subAdapter */
$subAdapter = $container->get($config['adapter']);
$subAdapter->attach($localAdapter->getEventManager());
$subAdapter->attach($localAdapter->getEventManager(), $priority);
}
}
\ No newline at end of file
......@@ -47,6 +47,18 @@ class ShibUser implements UserInterface
*/
protected $state = 1;
/**
* Retourne la partie domaine DNS de l'EPPN.
* Retourne par exemple "unicaen.fr" lorsque l'EPPN est "tartempion@unicaen.fr"
*
* @return string
*/
public function getEppnDomain()
{
$parts = explode('@', $this->getEppn());
return $parts[1];
}
/**
* @return string
......
......@@ -19,6 +19,10 @@ use Zend\Session\Container;
*/
class ShibService
{
const SHIB_USER_ID_EXTRACTOR = 'shib_user_id_extractor';
const DOMAIN_DEFAULT = 'default';
const KEY_fromShibUser = 'fromShibUser';
const KEY_toShibUser = 'toShibUser';
......@@ -105,9 +109,9 @@ EOS;
private function isAuthenticated(): bool
{
return
$this->getServerArrayVariable('REMOTE_USER') ||
$this->getServerArrayVariable('Shib-Session-ID') ||
$this->getServerArrayVariable('HTTP_SHIB_SESSION_ID');
$this->getValueFromShibData('REMOTE_USER', $_SERVER) ||
$this->getValueFromShibData('Shib-Session-ID', $_SERVER) ||
$this->getValueFromShibData('HTTP_SHIB_SESSION_ID', $_SERVER);
}
/**
......@@ -195,6 +199,36 @@ EOS;
return (array)$this->shibbolethConfig['required_attributes'];
}
/**
* @return array
*/
private function getShibUserIdExtractorDefaultConfig(): array
{
return $this->getShibUserIdExtractorConfigForDomain(self::DOMAIN_DEFAULT);
}
/**
* Retourne la config permettant d'extraire l'id à partir des attributs.
*
* @param string $domain
* @return array
*/
private function getShibUserIdExtractorConfigForDomain(string $domain): array
{
$key = self::SHIB_USER_ID_EXTRACTOR;
if (! array_key_exists($key, $this->shibbolethConfig)) {
throw new RuntimeException("Aucune config '$key' trouvée.");
}
$config = $this->shibbolethConfig[$key];
if (! array_key_exists($domain, $config)) {
return [];
}
return $config[$domain];
}
/**
*
*/
......@@ -224,7 +258,6 @@ EOS;
$this->assertRequiredAttributesExistInData($data);
$eppn = $this->getValueFromShibData('eppn', $data);
$supannId = $this->getValueFromShibData('supannEmpId', $data) ?: $this->getValueFromShibData('supannEtuId', $data);
$email = $this->getValueFromShibData('mail', $data);
$displayName = $this->getValueFromShibData('displayName', $data);
$givenName = $this->getValueFromShibData('givenName', $data);
......@@ -234,17 +267,22 @@ EOS;
Assertion::contains($eppn, '@', "L'eppn '" . $eppn . "' n'est pas de la forme 'id@domaine' attendue (ex: 'tartempion@unicaen.fr')");
$shibUser = new ShibUser();
// propriétés de UserInterface
$shibUser->setEppn($eppn);
$shibUser->setId($supannId);
$shibUser->setUsername($eppn);
$domain = $shibUser->getEppnDomain(); // possible uniquement après $shibUser->setEppn($eppn)
$id = $this->extractShibUserIdValueForDomainFromShibData($domain, $data);
$shibUser->setId($id);
$shibUser->setDisplayName($displayName);
$shibUser->setEmail($email);
// autres propriétés
$shibUser->setNom($surname);
$shibUser->setPrenom($givenName);
$shibUser->setCivilite($civilite);
return $shibUser;
}
/**
* Retourne true si les données stockées en session indiquent qu'une usurpation d'identité Shibboleth est en cours.
*
......@@ -332,7 +370,7 @@ EOS;
throw new RuntimeException("Anomalie: clé '$key' introuvable");
}
$this->simulateAuthenticatedUser($toShibUser, 'supannEmpId');
$this->simulateAuthenticatedUser($toShibUser);
return $this;
}
......@@ -394,26 +432,15 @@ EOS;
* qui se serait authentifié via Shibboleth.
*
* @param ShibUser $shibUser Utilisateur dont on veut usurper l'identité.
* @param string $keyForId Clé du tableau $_SERVER dans laquelle mettre l'id de l'utilsateur spécifié.
* Ex: 'supannEmpId', 'supannEtuId'.
*/
public function simulateAuthenticatedUser(ShibUser $shibUser, $keyForId = 'supannEmpId')
public function simulateAuthenticatedUser(ShibUser $shibUser)
{
// 'REMOTE_USER' (notamment) est utilisé pour savoir si un utilisateur est authentifié ou non
$this->setServerArrayVariable('REMOTE_USER', $shibUser->getEppn());
// // on s'assure que tous les attributs obligatoires ont une valeur
// foreach ($this->getShibbolethRequiredAttributes() as $requiredAttribute) {
// // un pipe permet d'exprimer un OU logique, ex: 'supannEmpId|supannEtuId'
// $attributes = array_map('trim', explode('|', $requiredAttribute));
// foreach ($attributes as $attribute) {
// $this->setServerArrayVariable($attribute, 'qqchose');
// }
// }
// pour certains attributs, on veut une valeur sensée!
$this->setServerArrayVariable('eppn', $shibUser->getEppn());
$this->setServerArrayVariable($keyForId, $shibUser->getId());
$this->setServerArrayVariable('supannEmpId', $shibUser->getId()); // ou bien 'supannEtuId', peu importe.
$this->setServerArrayVariable('displayName', $shibUser->getDisplayName());
$this->setServerArrayVariable('mail', $shibUser->getEppn());
$this->setServerArrayVariable('sn', $shibUser->getNom());
......@@ -432,18 +459,20 @@ EOS;
throw new RuntimeException('Des attributs Shibboleth obligatoires font défaut dans $_SERVER.', null, $e);
}
$eppn = $this->getServerArrayVariable('eppn');
$id = $this->getServerArrayVariable('supannEtuId') ?: $this->getServerArrayVariable('supannEmpId');
$mail = $this->getServerArrayVariable('mail');
$displayName = $this->getServerArrayVariable('displayName');
$surname = $this->getServerArrayVariable('sn') ?: $this->getServerArrayVariable('surname');
$givenName = $this->getServerArrayVariable('givenName');
$civilite = $this->getServerArrayVariable('supannCivilite');
$eppn = $this->getValueFromShibData('eppn', $_SERVER);
$mail = $this->getValueFromShibData('mail', $_SERVER);
$displayName = $this->getValueFromShibData('displayName', $_SERVER);
$surname = $this->getValueFromShibData('sn', $_SERVER) ?: $this->getValueFromShibData('surname', $_SERVER);
$givenName = $this->getValueFromShibData('givenName', $_SERVER);
$civilite = $this->getValueFromShibData('supannCivilite', $_SERVER);
$shibUser = new ShibUser();
// propriétés de UserInterface
$shibUser->setId($id);
$shibUser->setEppn($eppn);
$shibUser->setUsername($eppn);
$domain = $shibUser->getEppnDomain(); // possible uniquement après $shibUser->setEppn($eppn)
$id = $this->extractShibUserIdValueForDomainFromShibData($domain, $_SERVER);
$shibUser->setId($id);
$shibUser->setDisplayName($displayName);
$shibUser->setEmail($mail);
$shibUser->setPassword(null);
......@@ -455,6 +484,40 @@ EOS;
return $shibUser;
}
/**
* @param string $domain
* @param array $data
* @return string|null
*/
private function extractShibUserIdValueForDomainFromShibData(string $domain, array $data): ?string
{
$config = $this->getShibUserIdExtractorConfigForDomain($domain);
if (empty($config)) {
$config = $this->getShibUserIdExtractorDefaultConfig();
if (empty($config)) {
throw new RuntimeException("Aucune config trouvée ni pour le domaine '$domain' ni par défaut.");
}
}
foreach ($config as $array) {
$name = $array['name'];
$value = $this->getValueFromShibData($name, $data);
if ($value !== null) {
$pregMatchPattern = $array['preg_match_pattern'] ?? null;
if ($pregMatchPattern !== null) {
if (preg_match($pregMatchPattern, $value, $matches) === 0) {
throw new RuntimeException("Le pattern '$pregMatchPattern' n'a pas permis d'extraire une valeur de '$name'.");
}
$value = $matches[1];
}
return $value;
}
}
return null;
}
/**
* Retourne l'URL de déconnexion Shibboleth.
*
......@@ -554,19 +617,4 @@ EOS;
$_SERVER[$key] = $value;
}
/**
* @param $name
* @return string|null
*/
private function getServerArrayVariable($name): ?string
{
$key = $this->getShibbolethAliasFor($name) ?: $name;
if (! array_key_exists($key, $_SERVER)) {
return null;
}
return $_SERVER[$key];
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment