* bring back deprecated methods * bump packages * fix SAML redirect * config flag for break times * use class constant instead of string in attributes * throw if all tags were not found - fixes #4792
134 lines
4.7 KiB
PHP
134 lines
4.7 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of the Kimai time-tracking app.
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace App\Saml;
|
|
|
|
use App\Configuration\SamlConfigurationInterface;
|
|
use App\Saml\Security\SamlAuthenticationFailureHandler;
|
|
use App\Saml\Security\SamlAuthenticationSuccessHandler;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
|
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
|
|
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
|
|
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
|
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
|
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
|
use Symfony\Component\Security\Http\HttpUtils;
|
|
|
|
/**
|
|
* @final
|
|
*/
|
|
class SamlAuthenticator extends AbstractAuthenticator
|
|
{
|
|
private array $options = [
|
|
'check_path' => 'saml_acs',
|
|
'login_path' => 'saml_login',
|
|
'use_attribute_friendly_name' => false,
|
|
];
|
|
|
|
public function __construct(
|
|
private readonly HttpUtils $httpUtils,
|
|
private readonly SamlAuthenticationSuccessHandler $successHandler,
|
|
private readonly SamlAuthenticationFailureHandler $failureHandler,
|
|
private readonly SamlAuthFactory $samlAuthFactory,
|
|
private readonly SamlProvider $samlProvider,
|
|
private readonly SamlConfigurationInterface $configuration
|
|
) {
|
|
}
|
|
|
|
public function supports(Request $request): bool
|
|
{
|
|
if (!$this->configuration->isActivated()) {
|
|
return false;
|
|
}
|
|
|
|
if (!$request->isMethod(Request::METHOD_POST)) {
|
|
return false;
|
|
}
|
|
|
|
if (!$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function createToken(Passport $passport, string $firewallName): TokenInterface
|
|
{
|
|
$user = $passport->getUser();
|
|
|
|
$token = new SamlToken($user, $firewallName, $user->getRoles());
|
|
$token->setUser($user);
|
|
|
|
foreach ($passport->getBadges() as $badge) {
|
|
if ($badge instanceof SamlBadge) {
|
|
$token->setAttributes($badge->getSamlLoginAttributes()->getAttributes());
|
|
}
|
|
}
|
|
|
|
return $token;
|
|
}
|
|
|
|
public function authenticate(Request $request): Passport
|
|
{
|
|
$oneLoginAuth = $this->samlAuthFactory->create();
|
|
|
|
$oneLoginAuth->processResponse();
|
|
|
|
// file_put_contents(__DIR__ . '/../../var/log/saml.xml', $oneLoginAuth->getLastResponseXML());
|
|
|
|
if (\count($oneLoginAuth->getErrors()) > 0) {
|
|
throw new AuthenticationException($oneLoginAuth->getLastErrorReason());
|
|
}
|
|
|
|
$attributes = [];
|
|
if (isset($this->options['use_attribute_friendly_name']) && $this->options['use_attribute_friendly_name']) {
|
|
$attributes = $oneLoginAuth->getAttributesWithFriendlyName();
|
|
} else {
|
|
$attributes = $oneLoginAuth->getAttributes();
|
|
}
|
|
$attributes['sessionIndex'] = $oneLoginAuth->getSessionIndex();
|
|
|
|
$loginAttributes = new SamlLoginAttributes();
|
|
$loginAttributes->setAttributes($attributes);
|
|
|
|
if (isset($this->options['username_attribute'])) {
|
|
if (!\array_key_exists($this->options['username_attribute'], $attributes)) {
|
|
throw new \Exception(\sprintf("Attribute '%s' not found in SAML data", $this->options['username_attribute']));
|
|
}
|
|
|
|
$username = $attributes[$this->options['username_attribute']][0];
|
|
} else {
|
|
$username = $oneLoginAuth->getNameId();
|
|
}
|
|
$loginAttributes->setUserIdentifier($username);
|
|
|
|
$passport = new SelfValidatingPassport(
|
|
new UserBadge($loginAttributes->getUserIdentifier(), function () use ($loginAttributes) {
|
|
return $this->samlProvider->findUser($loginAttributes);
|
|
}),
|
|
[new RememberMeBadge(), new SamlBadge($loginAttributes)]
|
|
);
|
|
|
|
return $passport;
|
|
}
|
|
|
|
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
|
{
|
|
return $this->successHandler->onAuthenticationSuccess($request, $token);
|
|
}
|
|
|
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
|
|
{
|
|
return $this->failureHandler->onAuthenticationFailure($request, $exception);
|
|
}
|
|
}
|