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
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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
+12 −0
Original line number Diff line number Diff line
@@ -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',
            ],
            */
        ],

        /**
+134 −66
Original line number Diff line number Diff line
@@ -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
     */
+4 −3
Original line number Diff line number Diff line
@@ -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;
    }