SAML Cleanup, map sso requests to created sessions
This commit is contained in:
parent
ae0e37ebab
commit
01c0f84272
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user