diff --git a/CHANGELOG.md b/CHANGELOG.md index 18532dfff7f828c300072de3b4db691cf1ba5dfd..d01c0ffd179069efa71a262864f3a89fefb78996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,3 +17,8 @@ - Correction: l'utilisateur n'était pas recherché par son username! - Ajout d'un validateur sur le formulaire de saisie de l'adresse électronique. - 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 diff --git a/config/unicaen-auth.local.php.dist b/config/unicaen-auth.local.php.dist index 0c3826e4389948b413566a9e6daac8f09fb1b38a..7fe8f5a242764852825073ef4ac5b16fe4bd5837 100644 --- a/config/unicaen-auth.local.php.dist +++ b/config/unicaen-auth.local.php.dist @@ -23,6 +23,18 @@ return [ 'sn' => 'HTTP_SN', 'givenName' => 'HTTP_GIVENNAME', ], + /* + 'required_attributes' => [ + 'eppn', + 'mail', + 'eduPersonPrincipalName', + 'supannCivilite', + 'displayName', + 'sn|surname', // i.e. 'sn' ou 'surname' + 'givenName', + 'supannEtuId|supannEmpId', + ], + */ ], /** diff --git a/src/UnicaenAuth/Service/ShibService.php b/src/UnicaenAuth/Service/ShibService.php index e0df7413010c623424907afd95e63410c80409c9..513225f2ae3c603719be8c67d575f4fdbfb260a4 100644 --- a/src/UnicaenAuth/Service/ShibService.php +++ b/src/UnicaenAuth/Service/ShibService.php @@ -2,31 +2,36 @@ namespace UnicaenAuth\Service; +use Application\Exception\InvalidArgumentException; use Assert\Assertion; use Assert\AssertionFailedException; use UnicaenApp\Exception\LogicException; use UnicaenApp\Exception\RuntimeException; use UnicaenAuth\Entity\Shibboleth\ShibUser; -use UnicaenAuth\Options\ModuleOptions; use Zend\Mvc\Router\Http\TreeRouteStack; use Zend\Session\Container; /** - * Shibboleth service + * Shibboleth service. * * @author Unicaen */ 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 @@ -48,6 +53,22 @@ EOS; 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 */ @@ -75,9 +96,7 @@ EOS; */ public function isShibbolethEnabled() { - $options = $this->options->getShibboleth(); - - return array_key_exists('enable', $options) && (bool) $options['enable']; + return array_key_exists('enable', $this->shibbolethConfig) && (bool) $this->shibbolethConfig['enable']; } /** @@ -85,28 +104,44 @@ EOS; */ public function getShibbolethSimulate() { - $options = $this->options->getShibboleth(); - - if (! array_key_exists('simulate', $options) || ! is_array($options['simulate'])) { + if (! array_key_exists('simulate', $this->shibbolethConfig) || ! is_array($this->shibbolethConfig['simulate'])) { return []; } - return $options['simulate']; + return $this->shibbolethConfig['simulate']; } /** * @param string $attributeName * @return string */ - public function getShibbolethAliasFor($attributeName) + private function getShibbolethAliasFor($attributeName) { - $options = $this->options->getShibboleth(); - - if (! array_key_exists('aliases', $options) || ! is_array($options['aliases']) || ! isset($options['aliases'][$attributeName])) { + if (! array_key_exists('aliases', $this->shibbolethConfig) || + ! is_array($this->shibbolethConfig['aliases']) || + ! isset($this->shibbolethConfig['aliases'][$attributeName])) { 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; */ public function isSimulationActive() { - $options = $this->options->getShibboleth(); - - if (array_key_exists('simulate', $options) && is_array($options['simulate']) && ! empty($options['simulate'])) { + if (array_key_exists('simulate', $this->shibbolethConfig) && + is_array($this->shibbolethConfig['simulate']) && + ! empty($this->shibbolethConfig['simulate'])) { return true; } 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 */ @@ -186,7 +235,7 @@ EOS; public function activateUsurpation(ShibUser $fromShibUser, ShibUser $toShibUser) { // 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"); } @@ -241,9 +290,53 @@ EOS; 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 - * qui ce serait authentifié via Shibboleth. + * 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é. @@ -251,10 +344,16 @@ EOS; */ 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($keyForId, $shibUser->getId()); $this->setServerArrayVariable('displayName', $shibUser->getDisplayName()); - $this->setServerArrayVariable('mail', $shibUser->getEmail()); + $this->setServerArrayVariable('mail', $shibUser->getEppn()); $this->setServerArrayVariable('sn', $shibUser->getNom()); $this->setServerArrayVariable('givenName', $shibUser->getPrenom()); } @@ -264,42 +363,19 @@ EOS; */ private function createShibUserFromServerArrayData() { - $eppn = $this->getServerArrayVariable('eppn'); - - if ($value = $this->getServerArrayVariable('supannEtuId')) { - $id = $value; - } 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; + try { + $this->assertRequiredAttributesExistInData($_SERVER); + } catch (InvalidArgumentException $e) { + throw new RuntimeException('Des attributs Shibboleth obligatoires font défaut dans $_SERVER.', null, $e); } - $civilite = null; - if ($value = $this->getServerArrayVariable('supannCivilite')) { - $civilite = $value; - } + $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'); $shibUser = new ShibUser(); // propriétés de UserInterface @@ -340,14 +416,6 @@ EOS; return $logoutRelativeUrl; } - /** - * @param ModuleOptions $options - */ - public function setOptions(ModuleOptions $options) - { - $this->options = $options; - } - /** * @param TreeRouteStack $router */ diff --git a/src/UnicaenAuth/Service/ShibServiceFactory.php b/src/UnicaenAuth/Service/ShibServiceFactory.php index 418f717049ccb1b7477009a156456d05ca9062eb..ae9bed6b6138e1b268fa40098add777182bc5024 100644 --- a/src/UnicaenAuth/Service/ShibServiceFactory.php +++ b/src/UnicaenAuth/Service/ShibServiceFactory.php @@ -9,11 +9,12 @@ class ShibServiceFactory { public function __invoke(ServiceLocatorInterface $sl) { - /** @var ModuleOptions $options */ - $options = $sl->get('unicaen-auth_module_options'); + /** @var ModuleOptions $moduleOptions */ + $moduleOptions = $sl->get('unicaen-auth_module_options'); $service = new ShibService(); - $service->setOptions($options); + $service->setShibbolethConfig($moduleOptions->getShibboleth()); + $service->setUsurpationAllowedUsernames($moduleOptions->getUsurpationAllowedUsernames()); return $service; }