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 @@
- 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
......@@ -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',
],
*/
],
/**
......
......@@ -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
*/
......
......@@ -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;
}
......
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