SAML Cleanup, map sso requests to created sessions

This commit is contained in:
Roman 2024-12-31 14:25:36 +01:00
parent ae0e37ebab
commit 01c0f84272
3 changed files with 35 additions and 32 deletions

@ -4,6 +4,7 @@ namespace Core\API {
use Core\Objects\Context; use Core\Objects\Context;
use Core\Objects\DatabaseEntity\SsoProvider; use Core\Objects\DatabaseEntity\SsoProvider;
use Core\Objects\DatabaseEntity\SsoRequest;
use Core\Objects\DatabaseEntity\User; use Core\Objects\DatabaseEntity\User;
abstract class SsoAPI extends Request { abstract class SsoAPI extends Request {
@ -11,8 +12,9 @@ namespace Core\API {
parent::__construct($context, $externalCall, $params); parent::__construct($context, $externalCall, $params);
} }
protected function processLogin(SsoProvider $provider, User $user, ?string $redirectUrl): bool { protected function processLogin(SsoRequest $ssoRequest, User $user): bool {
$sql = $this->context->getSQL(); $sql = $this->context->getSQL();
$provider = $ssoRequest->getProvider();
if ($user->getId() === null) { if ($user->getId() === null) {
// user didn't exit yet. try to insert into database // user didn't exit yet. try to insert into database
if (!$user->save($sql)) { if (!$user->save($sql)) {
@ -29,6 +31,8 @@ namespace Core\API {
return false; return false;
} }
$ssoRequest->invalidate($sql, $this->context->getSession());
$redirectUrl = $ssoRequest->getRedirectUrl();
if (!empty($redirectUrl)) { if (!empty($redirectUrl)) {
$this->context->router->redirect(302, $redirectUrl); $this->context->router->redirect(302, $redirectUrl);
} }
@ -230,37 +234,18 @@ namespace Core\API\Sso {
} }
protected function _execute(): bool { protected function _execute(): bool {
$sql = $this->context->getSQL();
$samlResponse = base64_decode($this->getParam("SAMLResponse")); $samlResponse = base64_decode($this->getParam("SAMLResponse"));
$parsedResponse = SAMLResponse::parseResponse($this->context, $samlResponse); $parsedResponse = SAMLResponse::parseResponse($this->context, $samlResponse);
$ssoRequest = $parsedResponse->getRequest();
if (!$parsedResponse->wasSuccessful()) { if (!$parsedResponse->wasSuccessful()) {
$ssoRequest?->invalidate($sql);
return $this->createError("Error parsing SAMLResponse: " . $parsedResponse->getError()); return $this->createError("Error parsing SAMLResponse: " . $parsedResponse->getError());
} else if (!$this->processLogin($ssoRequest, $parsedResponse->getUser())) {
$ssoRequest->invalidate($sql);
return false;
} else { } else {
return $this->processLogin($parsedResponse->getProvider(), $parsedResponse->getUser(), $parsedResponse->getRedirectURL()); return true;
}
$sql = $this->context->getSQL();
$ssoProviderIdentifier = $this->getParam("provider");
$ssoProvider = SsoProvider::findBy(SsoProvider::createBuilder($sql, true)
->whereEq("identifier", $ssoProviderIdentifier)
->whereTrue("active")
);
if ($ssoProvider === false) {
return $this->createError("Error fetching SSO Provider: " . $sql->getLastError());
} else if ($ssoProvider === null) {
return $this->createError("SSO Provider not found");
}
$samlResponseEncoded = $this->getParam("SAMLResponse");
if (($samlResponse = @gzinflate(base64_decode($samlResponseEncoded))) === false) {
$samlResponse = base64_decode($samlResponseEncoded);
}
$parsedUser = $ssoProvider->parseResponse($this->context, $samlResponse);
if ($parsedUser === null) {
return $this->createError("Invalid SAMLResponse");
} else {
return $this->processLogin($parsedUser, $redirectUrl);
} }
} }

@ -12,12 +12,22 @@ class SsoRequest extends DatabaseEntity {
const SSO_REQUEST_DURABILITY = 15; // in minutes const SSO_REQUEST_DURABILITY = 15; // in minutes
// auto-delete sso requests after 30 days after creation
protected static array $entityLogConfig = [
"update" => false,
"delete" => true,
"insert" => true,
"lifetime" => 30
];
#[MaxLength(128)] #[MaxLength(128)]
#[Unique] #[Unique]
private string $identifier; private string $identifier;
private SsoProvider $ssoProvider; private SsoProvider $ssoProvider;
private ?Session $session;
private \DateTime $validUntil; private \DateTime $validUntil;
#[DefaultValue(false)] #[DefaultValue(false)]
@ -30,6 +40,7 @@ class SsoRequest extends DatabaseEntity {
$request->identifier = uuidv4(); $request->identifier = uuidv4();
$request->ssoProvider = $ssoProvider; $request->ssoProvider = $ssoProvider;
$request->used = false; $request->used = false;
$request->session = null;
$request->validUntil = (new \DateTime())->modify(sprintf('+%d minutes', self::SSO_REQUEST_DURABILITY)); $request->validUntil = (new \DateTime())->modify(sprintf('+%d minutes', self::SSO_REQUEST_DURABILITY));
$request->redirectUrl = $redirectUrl; $request->redirectUrl = $redirectUrl;
if ($request->save($sql)) { if ($request->save($sql)) {
@ -59,9 +70,14 @@ class SsoRequest extends DatabaseEntity {
return $this->ssoProvider; return $this->ssoProvider;
} }
public function invalidate(SQL $sql) : bool { public function invalidate(SQL $sql, ?Session $session = null) : bool {
$this->used = true; $this->used = true;
return $this->save($sql, ["used"]); if ($session) {
$this->session = $session;
return $this->save($sql, ["used", "session"]);
} else {
return $this->save($sql, ["used"]);
}
} }
} }

@ -57,7 +57,7 @@ class SAMLResponse {
}; };
} }
private static function verifyNodeSignature(SsoProvider $provider, \DOMNode $signatureNode) { private static function verifyNodeSignature(SsoProvider $provider, \DOMNode $signatureNode): void {
$signedInfoNode = $signatureNode->getElementsByTagName('SignedInfo')->item(0); $signedInfoNode = $signatureNode->getElementsByTagName('SignedInfo')->item(0);
if (!$signedInfoNode) { if (!$signedInfoNode) {
throw new \Exception("SignedInfo not found in the Signature element."); throw new \Exception("SignedInfo not found in the Signature element.");
@ -108,8 +108,6 @@ class SAMLResponse {
return self::createError($ssoRequest, "SAMLResponse already processed"); return self::createError($ssoRequest, "SAMLResponse already processed");
} else if (!$ssoRequest->isValid()) { } else if (!$ssoRequest->isValid()) {
return self::createError($ssoRequest, "Authentication request expired"); return self::createError($ssoRequest, "Authentication request expired");
} else {
$ssoRequest->invalidate($sql);
} }
try { try {
@ -199,4 +197,8 @@ class SAMLResponse {
return $this->request->getProvider(); return $this->request->getProvider();
} }
public function getRequest() : ?SsoRequest {
return $this->request;
}
} }