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

Authentification Shibboleth: possibilité de spécifier les attributs...

Authentification Shibboleth: possibilité de spécifier les attributs nécessaires au fonctionnement de l'appli (clé de config `unicaen-auth` > `shibboleth` > `required_attributes`).
parent 26dc3f97
Pipeline #3432 failed with stages
in 2 minutes and 10 seconds
...@@ -17,3 +17,8 @@ ...@@ -17,3 +17,8 @@
- Correction: l'utilisateur n'était pas recherché par son username! - Correction: l'utilisateur n'était pas recherché par son username!
- Ajout d'un validateur sur le formulaire de saisie de l'adresse électronique. - Ajout d'un validateur sur le formulaire de saisie de l'adresse électronique.
- Vérification que le compte utilisateur est bien local. - Vérification que le compte utilisateur est bien local.
## 1.3.2 - 29/01/2019
- Authentification Shibboleth: possibilité de spécifier les attributs nécessaires au fonctionnement de l'appli
(clé de config `unicaen-auth` > `shibboleth` > `required_attributes`).
\ No newline at end of file
...@@ -23,6 +23,18 @@ return [ ...@@ -23,6 +23,18 @@ return [
'sn' => 'HTTP_SN', 'sn' => 'HTTP_SN',
'givenName' => 'HTTP_GIVENNAME', 'givenName' => 'HTTP_GIVENNAME',
], ],
/*
'required_attributes' => [
'eppn',
'mail',
'eduPersonPrincipalName',
'supannCivilite',
'displayName',
'sn|surname', // i.e. 'sn' ou 'surname'
'givenName',
'supannEtuId|supannEmpId',
],
*/
], ],
/** /**
......
...@@ -2,31 +2,36 @@ ...@@ -2,31 +2,36 @@
namespace UnicaenAuth\Service; namespace UnicaenAuth\Service;
use Application\Exception\InvalidArgumentException;
use Assert\Assertion; use Assert\Assertion;
use Assert\AssertionFailedException; use Assert\AssertionFailedException;
use UnicaenApp\Exception\LogicException; use UnicaenApp\Exception\LogicException;
use UnicaenApp\Exception\RuntimeException; use UnicaenApp\Exception\RuntimeException;
use UnicaenAuth\Entity\Shibboleth\ShibUser; use UnicaenAuth\Entity\Shibboleth\ShibUser;
use UnicaenAuth\Options\ModuleOptions;
use Zend\Mvc\Router\Http\TreeRouteStack; use Zend\Mvc\Router\Http\TreeRouteStack;
use Zend\Session\Container; use Zend\Session\Container;
/** /**
* Shibboleth service * Shibboleth service.
* *
* @author Unicaen * @author Unicaen
*/ */
class ShibService class ShibService
{ {
/** /**
* @var ModuleOptions * @var \UnicaenAuth\Entity\Shibboleth\ShibUser
*/ */
protected $options; protected $authenticatedUser;
/** /**
* @var \UnicaenAuth\Entity\Shibboleth\ShibUser * @var array
*/ */
protected $authenticatedUser; protected $shibbolethConfig = [];
/**
* @var array
*/
protected $usurpationAllowedUsernames = [];
/** /**
* @return string * @return string
...@@ -48,6 +53,22 @@ EOS; ...@@ -48,6 +53,22 @@ EOS;
return $text; return $text;
} }
/**
* @param array $shibbolethConfig
*/
public function setShibbolethConfig(array $shibbolethConfig)
{
$this->shibbolethConfig = $shibbolethConfig;
}
/**
* @param array $usurpationAllowedUsernames
*/
public function setUsurpationAllowedUsernames(array $usurpationAllowedUsernames)
{
$this->usurpationAllowedUsernames = $usurpationAllowedUsernames;
}
/** /**
* @return ShibUser|null * @return ShibUser|null
*/ */
...@@ -75,9 +96,7 @@ EOS; ...@@ -75,9 +96,7 @@ EOS;
*/ */
public function isShibbolethEnabled() public function isShibbolethEnabled()
{ {
$options = $this->options->getShibboleth(); return array_key_exists('enable', $this->shibbolethConfig) && (bool) $this->shibbolethConfig['enable'];
return array_key_exists('enable', $options) && (bool) $options['enable'];
} }
/** /**
...@@ -85,28 +104,44 @@ EOS; ...@@ -85,28 +104,44 @@ EOS;
*/ */
public function getShibbolethSimulate() public function getShibbolethSimulate()
{ {
$options = $this->options->getShibboleth(); if (! array_key_exists('simulate', $this->shibbolethConfig) || ! is_array($this->shibbolethConfig['simulate'])) {
if (! array_key_exists('simulate', $options) || ! is_array($options['simulate'])) {
return []; return [];
} }
return $options['simulate']; return $this->shibbolethConfig['simulate'];
} }
/** /**
* @param string $attributeName * @param string $attributeName
* @return string * @return string
*/ */
public function getShibbolethAliasFor($attributeName) private function getShibbolethAliasFor($attributeName)
{ {
$options = $this->options->getShibboleth(); if (! array_key_exists('aliases', $this->shibbolethConfig) ||
! is_array($this->shibbolethConfig['aliases']) ||
if (! array_key_exists('aliases', $options) || ! is_array($options['aliases']) || ! isset($options['aliases'][$attributeName])) { ! isset($this->shibbolethConfig['aliases'][$attributeName])) {
return null; return null;
} }
return $options['aliases'][$attributeName]; return $this->shibbolethConfig['aliases'][$attributeName];
}
/**
* Retourne les alias des attributs spécifiés.
* Si un attribut n'a pas d'alias, c'est l'attribut lui-même qui est retourné.
*
* @param array $attributeNames
* @return array
*/
private function getAliasedShibbolethAttributes(array $attributeNames)
{
$aliasedAttributes = [];
foreach ($attributeNames as $attributeName) {
$alias = $this->getShibbolethAliasFor($attributeName);
$aliasedAttributes[$attributeName] = $alias ?: $attributeName;
}
return $aliasedAttributes;
} }
/** /**
...@@ -116,15 +151,29 @@ EOS; ...@@ -116,15 +151,29 @@ EOS;
*/ */
public function isSimulationActive() public function isSimulationActive()
{ {
$options = $this->options->getShibboleth(); if (array_key_exists('simulate', $this->shibbolethConfig) &&
is_array($this->shibbolethConfig['simulate']) &&
if (array_key_exists('simulate', $options) && is_array($options['simulate']) && ! empty($options['simulate'])) { ! empty($this->shibbolethConfig['simulate'])) {
return true; return true;
} }
return false; return false;
} }
/**
* Retourne la liste des attributs requis.
*
* @return array
*/
private function getShibbolethRequiredAttributes()
{
if (! array_key_exists('required_attributes', $this->shibbolethConfig)) {
return [];
}
return (array)$this->shibbolethConfig['required_attributes'];
}
/** /**
* @return ShibUser|null * @return ShibUser|null
*/ */
...@@ -186,7 +235,7 @@ EOS; ...@@ -186,7 +235,7 @@ EOS;
public function activateUsurpation(ShibUser $fromShibUser, ShibUser $toShibUser) public function activateUsurpation(ShibUser $fromShibUser, ShibUser $toShibUser)
{ {
// le login doit faire partie des usurpateurs autorisés // le login doit faire partie des usurpateurs autorisés
if (! in_array($fromShibUser->getUsername(), $this->options->getUsurpationAllowedUsernames())) { if (! in_array($fromShibUser->getUsername(), $this->usurpationAllowedUsernames)) {
throw new RuntimeException("Usurpation non autorisée"); throw new RuntimeException("Usurpation non autorisée");
} }
...@@ -241,9 +290,53 @@ EOS; ...@@ -241,9 +290,53 @@ EOS;
return new Container(ShibService::class); return new Container(ShibService::class);
} }
/**
* @param array $data
* @return array
*/
private function getMissingRequiredAttributesFromData(array $data)
{
$requiredAttributes = $this->getShibbolethRequiredAttributes();
$missingAttributes = [];
foreach ($requiredAttributes as $requiredAttribute) {
// un pipe permet d'exprimer un OU logique, ex: 'supannEmpId|supannEtuId'
$attributes = array_map('trim', explode('|', $requiredAttribute));
// attributs aliasés
$attributes = $this->getAliasedShibbolethAttributes($attributes);
$found = false;
foreach (array_map('trim', $attributes) as $attribute) {
if (isset($data[$attribute])) {
$found = true;
}
}
if (!$found) {
// attributs aliasés, dont l'un au moins est manquant, mise sous forme 'a|b'
$missingAttributes[] = implode('|', $attributes);
}
}
return $missingAttributes;
}
/**
* @param array $data
* @throws InvalidArgumentException
*/
private function assertRequiredAttributesExistInData(array $data)
{
$missingAttributes = $this->getMissingRequiredAttributesFromData($data);
if (!empty($missingAttributes)) {
throw new InvalidArgumentException(
"Les attributs suivants sont manquants : " . implode(', ', $missingAttributes));
}
}
/** /**
* Inscrit dans le tableau $_SERVER le nécessaire pour usurper l'identité d'un utilisateur * Inscrit dans le tableau $_SERVER le nécessaire pour usurper l'identité d'un utilisateur
* qui ce serait authentifié via Shibboleth. * qui se serait authentifié via Shibboleth.
* *
* @param ShibUser $shibUser Utilisateur dont on veut usurper l'identité. * @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é. * @param string $keyForId Clé du tableau $_SERVER dans laquelle mettre l'id de l'utilsateur spécifié.
...@@ -251,10 +344,16 @@ EOS; ...@@ -251,10 +344,16 @@ EOS;
*/ */
public function simulateAuthenticatedUser(ShibUser $shibUser, $keyForId = 'supannEmpId') public function simulateAuthenticatedUser(ShibUser $shibUser, $keyForId = 'supannEmpId')
{ {
// on s'assure que tous les attributs obligatoires ont une valeur
foreach ($this->getShibbolethRequiredAttributes() as $attribute) {
$this->setServerArrayVariable($attribute, 'qqchose');
}
// pour certains attributs, on veut une valeur sensée!
$this->setServerArrayVariable('eppn', $shibUser->getEppn()); $this->setServerArrayVariable('eppn', $shibUser->getEppn());
$this->setServerArrayVariable($keyForId, $shibUser->getId()); $this->setServerArrayVariable($keyForId, $shibUser->getId());
$this->setServerArrayVariable('displayName', $shibUser->getDisplayName()); $this->setServerArrayVariable('displayName', $shibUser->getDisplayName());
$this->setServerArrayVariable('mail', $shibUser->getEmail()); $this->setServerArrayVariable('mail', $shibUser->getEppn());
$this->setServerArrayVariable('sn', $shibUser->getNom()); $this->setServerArrayVariable('sn', $shibUser->getNom());
$this->setServerArrayVariable('givenName', $shibUser->getPrenom()); $this->setServerArrayVariable('givenName', $shibUser->getPrenom());
} }
...@@ -264,42 +363,19 @@ EOS; ...@@ -264,42 +363,19 @@ EOS;
*/ */
private function createShibUserFromServerArrayData() private function createShibUserFromServerArrayData()
{ {
$eppn = $this->getServerArrayVariable('eppn'); try {
$this->assertRequiredAttributesExistInData($_SERVER);
if ($value = $this->getServerArrayVariable('supannEtuId')) { } catch (InvalidArgumentException $e) {
$id = $value; throw new RuntimeException('Des attributs Shibboleth obligatoires font défaut dans $_SERVER.', null, $e);
} elseif ($value = $this->getServerArrayVariable('supannEmpId')) {
$id = $value;
} else {
throw new RuntimeException('Un au moins des attributs Shibboleth suivants doit exister dans $_SERVER : supannEtuId, supannEmpId.');
}
$mail = null;
if ($value = $this->getServerArrayVariable('mail')) {
$mail = $value;
}
$displayName = null;
if ($value = $this->getServerArrayVariable('displayName')) {
$displayName = $value;
}
$surname = null;
if ($value = $this->getServerArrayVariable('sn')) {
$surname = $value;
} elseif ($value = $this->getServerArrayVariable('surname')) {
$surname = $value;
}
$givenName = null;
if ($value = $this->getServerArrayVariable('givenName')) {
$givenName = $value;
} }
$civilite = null; $eppn = $this->getServerArrayVariable('eppn');
if ($value = $this->getServerArrayVariable('supannCivilite')) { $id = $this->getServerArrayVariable('supannEtuId') ?: $this->getServerArrayVariable('supannEmpId');
$civilite = $value; $mail = $this->getServerArrayVariable('mail');
} $displayName = $this->getServerArrayVariable('displayName');
$surname = $this->getServerArrayVariable('sn') ?: $this->getServerArrayVariable('surname');
$givenName = $this->getServerArrayVariable('givenName');
$civilite = $this->getServerArrayVariable('supannCivilite');
$shibUser = new ShibUser(); $shibUser = new ShibUser();
// propriétés de UserInterface // propriétés de UserInterface
...@@ -340,14 +416,6 @@ EOS; ...@@ -340,14 +416,6 @@ EOS;
return $logoutRelativeUrl; return $logoutRelativeUrl;
} }
/**
* @param ModuleOptions $options
*/
public function setOptions(ModuleOptions $options)
{
$this->options = $options;
}
/** /**
* @param TreeRouteStack $router * @param TreeRouteStack $router
*/ */
......
...@@ -9,11 +9,12 @@ class ShibServiceFactory ...@@ -9,11 +9,12 @@ class ShibServiceFactory
{ {
public function __invoke(ServiceLocatorInterface $sl) public function __invoke(ServiceLocatorInterface $sl)
{ {
/** @var ModuleOptions $options */ /** @var ModuleOptions $moduleOptions */
$options = $sl->get('unicaen-auth_module_options'); $moduleOptions = $sl->get('unicaen-auth_module_options');
$service = new ShibService(); $service = new ShibService();
$service->setOptions($options); $service->setShibbolethConfig($moduleOptions->getShibboleth());
$service->setUsurpationAllowedUsernames($moduleOptions->getUsurpationAllowedUsernames());
return $service; return $service;
} }
......
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