-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
398 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SimpleSAML\SAML2; | ||
|
||
/** | ||
* Class for SAML artifacts. | ||
* | ||
* @package simplesamlphp/saml2 | ||
*/ | ||
final class Artifact | ||
{ | ||
/** | ||
* Initialize an artifact. | ||
* | ||
* @param string $artifact | ||
* @param int $endpointIndex | ||
* @param string $sourceId | ||
*/ | ||
public function __construct( | ||
protected string $artifact, | ||
protected int $endpointIndex, | ||
protected string $sourceId, | ||
) { | ||
} | ||
|
||
|
||
/** | ||
* Collect the value of the artifact-property | ||
* | ||
* @return string | ||
*/ | ||
public function getArtifact: string | ||
{ | ||
return $this->artifact; | ||
} | ||
|
||
|
||
/** | ||
* Collect the value of the endpointIndex-property | ||
* | ||
* @return int | ||
*/ | ||
public function getEndpointIndex(): int | ||
{ | ||
return $this->endpointIndex; | ||
} | ||
|
||
|
||
/** | ||
* Collect the value of the sourceId-property | ||
* | ||
* @return string | ||
*/ | ||
public function getSourceId(): string | ||
{ | ||
return $this->sourceId; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SimpleSAML\SAML2\Entity; | ||
|
||
use Psr\Http\Message\ServerRequestInterface; | ||
use SimpleSAML\SAML2\Exception\MetadataNotFoundException; | ||
use SimpleSAML\SAML2\Exception\Protocol\ResourceNotRecognizedException; | ||
use SimpleSAML\SAML2\Exception\RemoteException; | ||
use SimpleSAML\SAML2\Metadata; | ||
use SimpleSAML\SAML2\XML\samlp\Response; | ||
|
||
/** | ||
* Class representing a SAML 2 Service Provider. | ||
* | ||
* @package simplesamlphp/saml2 | ||
*/ | ||
abstract class ServiceProvider | ||
{ | ||
protected ?Response $validatedResponse; | ||
|
||
|
||
/** | ||
* @param \SimpleSAML\SAML2\Metadata\ServiceProvider $spMetadata | ||
* @param bool $encryptedAssertions Whether assertions must be encrypted | ||
* @param bool $disableScoping Wheter to send the samlp:Scoping element in requests | ||
* @param bool $enableUnsolicited Wheter to process unsolicited responses | ||
* @param bool $encryptNameId Whether to encrypt the NameID sent | ||
* @param bool $signAuthnRequest Whether to sign the AuthnRequest sent | ||
* @param bool $signLogout Whether to sign the LogoutRequest/LogoutResponse sent | ||
* @param bool $validateLogout Whether to validate the signature of LogoutRequest/LogoutResponse received | ||
*/ | ||
public function __construct( | ||
protected readonly Metadata\ServiceProvider $spMetadata, | ||
protected readonly bool $encryptedAssertions = false, | ||
protected readonly bool $disableScoping = false, | ||
protected readonly bool $enableUnsolicited = false, | ||
protected readonly bool $encryptNameId = false, | ||
protected readonly bool $signAuthnRequest = false, | ||
protected readonly bool $signLogout = false, | ||
protected readonly bool $validateLogout = true, | ||
) { | ||
} | ||
|
||
|
||
/** | ||
* Receive a validated response. | ||
* | ||
* @param \Psr\Http\Message\ServerRequestInterface $request | ||
* @return \SimpleSAML\SAML2\XML\samlp\Response The validated response. | ||
* | ||
* @throws \SimpleSAML\SAML2\Exception\Protocol\UnsupportedBindingException | ||
*/ | ||
public function receiveValidatedResponse(ServerRequestInterface $request): Response | ||
{ | ||
$b = Binding::getCurrentBinding($request); | ||
|
||
if ($b instanceof HTTPArtifact) { | ||
$artifact = $b->receiveArtifact($request); | ||
$idpMetadata = $this->getMetadataForSha1($artifact->getSourceId()); | ||
|
||
if ($idpMetadata === null) { | ||
throw new MetadataNotFoundException(sprintf( | ||
'No metadata found for remote provider with SHA1 ID: %s', | ||
$artifact->getSourceId(), | ||
)); | ||
} | ||
|
||
$b->setIdpMetadata($idpMetadata); | ||
$b->setSPMetadata($this->spMetadata); | ||
} | ||
|
||
$response = $b->receive($request); | ||
Assert::isInstanceOf($response, Response::class, ResourceNotRecognizedException::class); | ||
|
||
// Validate the signature (if any) | ||
$this->ValidatedResponse = $this->validateResponseSignature($response); | ||
|
||
// Validate that the destination matches the appropriate endpoint from the SP-metadata | ||
$this->validateResponseDestination($b); | ||
|
||
// Validate that the status is 'success' | ||
$this->validateResponseStatus(); | ||
|
||
// TODO: validate the assertion | ||
// TODO: create a new Response-object with the validated Assertion in it and return it to the implementation | ||
} | ||
|
||
|
||
/** | ||
* Validate the status of the received response. | ||
* | ||
*/ | ||
private function validateResponseStatus(): void | ||
{ | ||
if (!$this->validatedResponse->isSuccess()) { | ||
throw new RemoteException($this->validatedResponse->getStatus()); | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Validate the destination of the received response. | ||
* | ||
* @param \SimpleSAML\SAML2\Binding $binding | ||
* @throws \SimpleSAML\SAML2\Exception\DestinationMismatchException | ||
*/ | ||
private function validateResponseDestination(Binding $b): void | ||
{ | ||
foreach ($this->spMetadata->getAssertionConsumerService() as $assertionConsumerService) { | ||
if ($assertionConsumerService->getLocation() === $this->validatedResponse->getDestination()) { | ||
if (Binding::getBinding($assertionConsumerService->getBinding()) instanceof $b) { | ||
return; | ||
} | ||
} | ||
} | ||
|
||
throw new ResourceNotRecognizedException(); | ||
} | ||
|
||
|
||
/** | ||
* Validate the signature of the received response. | ||
* | ||
* @param \SimpleSAML\SAML2\XML\samlp\Response $response | ||
* @return \SimpleSAML\SAML2\XML\samlp\Response The validated response. | ||
* | ||
* @throws \SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException | ||
*/ | ||
private function validateResponseSignature(Response $response): Response | ||
{ | ||
// Validate the signature on the response, if any | ||
if (!$response->isSigned()) { | ||
return $response; | ||
} | ||
|
||
$factory = $this->spMetadata->getSignatureAlgorithmFactory(); | ||
$signatureAlgorithm = $response->getSignature()->getSignedInfo()->getSignatureMethod()->getAlgorithm(); | ||
foreach ($this->spMetadata->getValidatingKeys() as $validatingKey) { | ||
$verifier = $factory->getAlgorithm($signatureAlgorithm, $validatingKey); | ||
|
||
try { | ||
return $response->verify($verifier); | ||
} catch (SignatureVerificationFailedException $e) { | ||
continue; | ||
} | ||
} | ||
|
||
throw new SignatureVerificationFailedException(); | ||
} | ||
|
||
|
||
/** | ||
* Find IdP-metadata based on a SHA-1 hash of the entityID. Return `null` if not found. | ||
*/ | ||
abstract protected function getIdPMetadataForSha1(string $sourceId): ?Metadata\IdentityProvider; | ||
|
||
|
||
/** | ||
* Find IdP-metadata based on an entityID. Return `null` if not found. | ||
*/ | ||
abstract protected function getIdPMetadata(string $entityId): ?Metadata\IdentityProvider; | ||
|
||
|
||
/** | ||
* Find SP-metadata based on an entityID. Return `null` if not found. | ||
*/ | ||
abstract protected function getSPMetadata(string $entityId): ?Metadata\ServiceProvider; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SimpleSAML\SAML2\Exception; | ||
|
||
/** | ||
* Exception to be raised when no metadata was found for a specific entityID | ||
*/ | ||
class MetadataNotFound extends RuntimeException | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SimpleSAML\SAML2\Exception; | ||
|
||
use SimpleSAML\SAML2\XML\samlp\Status; | ||
|
||
use function sprintf; | ||
|
||
/** | ||
* Exception to be raised when a status other than 'success' was received. | ||
*/ | ||
class RemoteException extends RuntimeException | ||
{ | ||
public function __construct(Status $status) | ||
{ | ||
$statusCode = $status->getStatusCode(); | ||
$message = $statusCode->getValue(); | ||
|
||
// Until proven necessary, we go just one level deep | ||
foreach ($statusCode->getSubCode() as $subCode) { | ||
$message = sprintf("%s / %s", $message, $subCode->getValue()); | ||
} | ||
|
||
$message = sprintf("%s (%s)", $message, $status->getStatusMessage()->getValue()); | ||
|
||
parent::__construct($message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.