2023-02-20 23:04:20 +01:00
|
|
|
<?php
|
|
|
|
/*
|
|
|
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
|
|
|
*
|
|
|
|
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as published
|
|
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace App\Security;
|
|
|
|
|
2023-02-27 23:47:42 +01:00
|
|
|
use App\Entity\UserSystem\Group;
|
2023-02-20 23:04:20 +01:00
|
|
|
use App\Entity\UserSystem\User;
|
2023-02-27 23:47:42 +01:00
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
2023-05-27 20:46:02 +02:00
|
|
|
use Nbgrp\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken;
|
|
|
|
use Nbgrp\OneloginSamlBundle\Security\User\SamlUserFactoryInterface;
|
2023-02-27 23:47:42 +01:00
|
|
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
|
|
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
|
2023-02-20 23:04:20 +01:00
|
|
|
use Symfony\Component\Security\Core\User\UserInterface;
|
|
|
|
|
2023-02-27 23:47:42 +01:00
|
|
|
class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterface
|
2023-02-20 23:04:20 +01:00
|
|
|
{
|
2023-06-11 14:15:46 +02:00
|
|
|
private readonly array $saml_role_mapping;
|
2023-02-27 23:47:42 +01:00
|
|
|
|
2023-06-11 14:15:46 +02:00
|
|
|
public function __construct(private readonly EntityManagerInterface $em, ?array $saml_role_mapping, private readonly bool $update_group_on_login)
|
2023-02-27 23:47:42 +01:00
|
|
|
{
|
2023-06-11 14:15:46 +02:00
|
|
|
$this->saml_role_mapping = $saml_role_mapping ?: [];
|
2023-02-27 23:47:42 +01:00
|
|
|
}
|
|
|
|
|
2023-06-11 14:15:46 +02:00
|
|
|
final public const SAML_PASSWORD_PLACEHOLDER = '!!SAML!!';
|
2023-02-23 23:36:40 +01:00
|
|
|
|
2023-02-20 23:04:20 +01:00
|
|
|
public function createUser($username, array $attributes = []): UserInterface
|
|
|
|
{
|
|
|
|
$user = new User();
|
|
|
|
$user->setName($username);
|
|
|
|
$user->setNeedPwChange(false);
|
2023-02-23 23:36:40 +01:00
|
|
|
$user->setPassword(self::SAML_PASSWORD_PLACEHOLDER);
|
2023-02-21 00:29:50 +01:00
|
|
|
//This is a SAML user now!
|
|
|
|
$user->setSamlUser(true);
|
2023-02-20 23:04:20 +01:00
|
|
|
|
2023-02-27 23:47:42 +01:00
|
|
|
//Update basic user information
|
2023-02-24 00:12:44 +01:00
|
|
|
$user->setSamlAttributes($attributes);
|
2023-02-20 23:04:20 +01:00
|
|
|
|
2023-02-27 23:47:42 +01:00
|
|
|
//Check if we can find a group for this user based on the SAML attributes
|
|
|
|
$group = $this->mapSAMLAttributesToLocalGroup($attributes);
|
|
|
|
$user->setGroup($group);
|
|
|
|
|
2023-02-20 23:04:20 +01:00
|
|
|
return $user;
|
|
|
|
}
|
2023-02-27 23:47:42 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This method is called after a successful authentication. It is used to update the group of the user,
|
|
|
|
* based on the new SAML attributes.
|
|
|
|
*/
|
|
|
|
public function onAuthenticationSuccess(AuthenticationSuccessEvent $event): void
|
|
|
|
{
|
|
|
|
if (! $this->update_group_on_login) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$token = $event->getAuthenticationToken();
|
|
|
|
$user = $token->getUser();
|
|
|
|
//Only update the group if the user is a SAML user
|
|
|
|
if (! $token instanceof SamlToken || ! $user instanceof User) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Check if we can find a group for this user based on the SAML attributes
|
|
|
|
$group = $this->mapSAMLAttributesToLocalGroup($token->getAttributes());
|
|
|
|
//If needed update the group of the user and save it to DB
|
|
|
|
if ($group !== $user->getGroup()) {
|
|
|
|
$user->setGroup($group);
|
2023-02-28 17:03:57 +01:00
|
|
|
$this->em->flush();
|
2023-02-27 23:47:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maps the given SAML attributes to a local group.
|
|
|
|
* @param array $attributes The SAML attributes
|
|
|
|
*/
|
|
|
|
public function mapSAMLAttributesToLocalGroup(array $attributes): ?Group
|
|
|
|
{
|
|
|
|
//Extract the roles from the SAML attributes
|
|
|
|
$roles = $attributes['group'] ?? [];
|
|
|
|
$group_id = $this->mapSAMLRolesToLocalGroupID($roles);
|
|
|
|
|
|
|
|
//Check if we can find a group with the given ID
|
|
|
|
if ($group_id !== null) {
|
|
|
|
$group = $this->em->find(Group::class, $group_id);
|
2023-06-11 14:55:06 +02:00
|
|
|
if ($group instanceof Group) {
|
2023-02-27 23:47:42 +01:00
|
|
|
return $group;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//If no group was found, return null
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maps a list of SAML roles to a local group ID.
|
2023-03-04 16:52:17 +01:00
|
|
|
* The first available mapping will be used (so the order of the $map is important, first match wins).
|
2023-02-27 23:47:42 +01:00
|
|
|
* @param array $roles The list of SAML roles
|
|
|
|
* @param array $map|null The mapping from SAML roles. If null, the global mapping will be used.
|
|
|
|
* @return int|null The ID of the local group or null if no mapping was found.
|
|
|
|
*/
|
|
|
|
public function mapSAMLRolesToLocalGroupID(array $roles, array $map = null): ?int
|
|
|
|
{
|
2023-06-11 14:15:46 +02:00
|
|
|
$map ??= $this->saml_role_mapping;
|
2023-02-27 23:47:42 +01:00
|
|
|
|
2023-03-04 16:52:17 +01:00
|
|
|
//Iterate over the mapping (from first to last) and check if we have a match
|
|
|
|
foreach ($map as $saml_role => $group_id) {
|
|
|
|
//Skip wildcard
|
|
|
|
if ($saml_role === '*') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (in_array($saml_role, $roles, true)) {
|
|
|
|
return (int) $group_id;
|
2023-02-27 23:47:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-04 16:52:17 +01:00
|
|
|
|
2023-02-27 23:47:42 +01:00
|
|
|
//If no applicable mapping was found, check if we have a default mapping
|
|
|
|
if (array_key_exists('*', $map)) {
|
|
|
|
return (int) $map['*'];
|
|
|
|
}
|
|
|
|
|
|
|
|
//If no mapping was found, return null
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getSubscribedEvents(): array
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
AuthenticationSuccessEvent::class => 'onAuthenticationSuccess',
|
|
|
|
];
|
|
|
|
}
|
2023-02-20 23:04:20 +01:00
|
|
|
}
|