diff --git a/Core/API/SsoAPI.class.php b/Core/API/SsoAPI.class.php index 2c4f775..611d7df 100644 --- a/Core/API/SsoAPI.class.php +++ b/Core/API/SsoAPI.class.php @@ -4,6 +4,7 @@ namespace Core\API { use Core\Objects\Context; use Core\Objects\DatabaseEntity\SsoProvider; + use Core\Objects\DatabaseEntity\SsoRequest; use Core\Objects\DatabaseEntity\User; abstract class SsoAPI extends Request { @@ -11,8 +12,9 @@ namespace Core\API { 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(); + $provider = $ssoRequest->getProvider(); if ($user->getId() === null) { // user didn't exit yet. try to insert into database if (!$user->save($sql)) { @@ -29,6 +31,8 @@ namespace Core\API { return false; } + $ssoRequest->invalidate($sql, $this->context->getSession()); + $redirectUrl = $ssoRequest->getRedirectUrl(); if (!empty($redirectUrl)) { $this->context->router->redirect(302, $redirectUrl); } @@ -230,37 +234,18 @@ namespace Core\API\Sso { } protected function _execute(): bool { - + $sql = $this->context->getSQL(); $samlResponse = base64_decode($this->getParam("SAMLResponse")); $parsedResponse = SAMLResponse::parseResponse($this->context, $samlResponse); + $ssoRequest = $parsedResponse->getRequest(); if (!$parsedResponse->wasSuccessful()) { + $ssoRequest?->invalidate($sql); return $this->createError("Error parsing SAMLResponse: " . $parsedResponse->getError()); + } else if (!$this->processLogin($ssoRequest, $parsedResponse->getUser())) { + $ssoRequest->invalidate($sql); + return false; } else { - return $this->processLogin($parsedResponse->getProvider(), $parsedResponse->getUser(), $parsedResponse->getRedirectURL()); - } - - $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); + return true; } } diff --git a/Core/Objects/DatabaseEntity/SsoRequest.class.php b/Core/Objects/DatabaseEntity/SsoRequest.class.php index b000764..8f9b256 100644 --- a/Core/Objects/DatabaseEntity/SsoRequest.class.php +++ b/Core/Objects/DatabaseEntity/SsoRequest.class.php @@ -12,12 +12,22 @@ class SsoRequest extends DatabaseEntity { 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)] #[Unique] private string $identifier; private SsoProvider $ssoProvider; + private ?Session $session; + private \DateTime $validUntil; #[DefaultValue(false)] @@ -30,6 +40,7 @@ class SsoRequest extends DatabaseEntity { $request->identifier = uuidv4(); $request->ssoProvider = $ssoProvider; $request->used = false; + $request->session = null; $request->validUntil = (new \DateTime())->modify(sprintf('+%d minutes', self::SSO_REQUEST_DURABILITY)); $request->redirectUrl = $redirectUrl; if ($request->save($sql)) { @@ -59,9 +70,14 @@ class SsoRequest extends DatabaseEntity { return $this->ssoProvider; } - public function invalidate(SQL $sql) : bool { + public function invalidate(SQL $sql, ?Session $session = null) : bool { $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"]); + } } } \ No newline at end of file diff --git a/Core/Objects/SSO/SAMLResponse.class.php b/Core/Objects/SSO/SAMLResponse.class.php index 67ed0a6..a069459 100644 --- a/Core/Objects/SSO/SAMLResponse.class.php +++ b/Core/Objects/SSO/SAMLResponse.class.php @@ -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); if (!$signedInfoNode) { throw new \Exception("SignedInfo not found in the Signature element."); @@ -108,8 +108,6 @@ class SAMLResponse { return self::createError($ssoRequest, "SAMLResponse already processed"); } else if (!$ssoRequest->isValid()) { return self::createError($ssoRequest, "Authentication request expired"); - } else { - $ssoRequest->invalidate($sql); } try { @@ -199,4 +197,8 @@ class SAMLResponse { return $this->request->getProvider(); } + public function getRequest() : ?SsoRequest { + return $this->request; + } + } \ No newline at end of file