Skip to content

Commit

Permalink
Feature/PSR-7 (#326)
Browse files Browse the repository at this point in the history
* Migrate bindings to PSR-7
  • Loading branch information
tvdijen committed Mar 7, 2023
1 parent bac9ee2 commit 55799d8
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 114 deletions.
46 changes: 24 additions & 22 deletions src/SAML2/Binding.php
Expand Up @@ -5,6 +5,7 @@
namespace SimpleSAML\SAML2;

use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use SimpleSAML\SAML2\Constants as C;
use SimpleSAML\SAML2\XML\samlp\AbstractMessage;
Expand Down Expand Up @@ -77,7 +78,6 @@ public static function getBinding(string $urn): Binding
*/
public static function getCurrentBinding(ServerRequestInterface $request): Binding
{
$headers = $request->getHeaders();
$method = $request->getMethod();

switch ($method) {
Expand All @@ -91,48 +91,49 @@ public static function getCurrentBinding(ServerRequestInterface $request): Bindi
break;

case 'POST':
if (isset($headers['CONTENT_TYPE'])) {
$contentType = $headers['CONTENT_TYPE'][0];
$contentType = null;
if ($request->hasHeader('Content-Type')) {
$contentType = $request->getHeader('Content-Type')[0];
$contentType = explode(';', $contentType);
$contentType = $contentType[0]; /* Remove charset. */
} else {
$contentType = null;
}

$query = $request->getParsedBody();
if (array_key_exists('SAMLRequest', $query) || array_key_exists('SAMLResponse', $query)) {
return new HTTPPost();
} elseif (array_key_exists('SAMLart', $query)) {
return new HTTPArtifact();
} elseif (
} else {
/**
* The registration information for text/xml is in all respects the same
* as that given for application/xml (RFC 7303 - Section 9.1)
*/
($contentType === 'text/xml' || $contentType === 'application/xml')
// See paragraph 3.2.3 of Binding for SAML2 (OASIS)
|| (
isset($_SERVER['HTTP_SOAPACTION'])
&& $_SERVER['HTTP_SOAPACTION'] === 'http://www.oasis-open.org/committees/security'
)
) {
return new SOAP();
if (
($contentType === 'text/xml' || $contentType === 'application/xml')
// See paragraph 3.2.3 of Binding for SAML2 (OASIS)
|| (
$request->hasHeader('SOAPAction')
&& $request->getHeader('SOAPAction')[0] === 'http://www.oasis-open.org/committees/security'
)
) {
return new SOAP();
}
}
break;
}

$logger = Utils::getContainer()->getLogger();
$logger->warning('Unable to find the SAML 2 binding used for this request.');
$logger->warning('Request method: ' . var_export($method, true));

if (!empty($query)) {
$logger->warning(sprintf(
'%s parameters: \'%s\'',
$method,
implode("', '", array_map('addslashes', array_keys($query))),
));
$logger->warning(
$method . " parameters: '" . implode("', '", array_map('addslashes', array_keys($query))) . "'"
);
}
if (isset($headers['CONTENT_TYPE'])) {
$logger->warning('Content-Type: ' . var_export($headers['CONTENT_TYPE'], true));

if ($request->hasHeader('Content-Type')) {
$logger->warning('Content-Type: ' . var_export($request->getHeader('Content-Type')[0], true));
}

throw new UnsupportedBindingException('Unable to find the SAML 2 binding used for this request.');
Expand Down Expand Up @@ -170,8 +171,9 @@ public function setDestination(string $destination = null): void
* The message will be delivered to the destination set in the message.
*
* @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message which should be sent.
* @return \Psr\Http\Message\ResponseInterface
*/
abstract public function send(AbstractMessage $message): void;
abstract public function send(AbstractMessage $message): ResponseInterface;


/**
Expand Down
12 changes: 2 additions & 10 deletions src/SAML2/Compat/AbstractContainer.php
Expand Up @@ -146,22 +146,14 @@ abstract public function generateId(): string;
abstract public function debugMessage($message, string $type): void;


/**
* Trigger the user to perform a GET to the given URL with the given data.
*
* @param string $url
* @param array $data
*/
abstract public function redirect(string $url, array $data = []): void;


/**
* Trigger the user to perform a POST to the given URL with the given data.
*
* @param string $url
* @param array $data
* @return string
*/
abstract public function postRedirect(string $url, array $data = []): void;
abstract public function getPOSTRedirectURL(string $url, array $data = []): string;


/**
Expand Down
43 changes: 6 additions & 37 deletions src/SAML2/Compat/MockContainer.php
Expand Up @@ -26,26 +26,6 @@ class MockContainer extends AbstractContainer
*/
private array $debugMessages = [];

/**
* @var string
*/
private string $redirectUrl;

/**
* @var array
*/
private array $redirectData = [];

/**
* @var string|null
*/
private ?string $postRedirectUrl = null;

/**
* @var array
*/
private array $postRedirectData;


/**
* Get a PSR-3 compatible logger.
Expand Down Expand Up @@ -85,29 +65,18 @@ public function debugMessage($message, string $type): void
}


/**
* Trigger the user to perform a GET to the given URL with the given data.
*
* @param string $url
* @param array $data
*/
public function redirect(string $url, array $data = []): void
{
$this->redirectUrl = $url;
$this->redirectData = $data;
}


/**
* Trigger the user to perform a POST to the given URL with the given data.
*
* @param string|null $url
* @param array $data
* @return string
*/
public function postRedirect(string $url = null, array $data = []): void
{
$this->postRedirectUrl = $url;
$this->postRedirectData = $data;
public function getPOSTRedirectURL(
/** @scrutinizer ignore-unused */string $url = null,
/** @scrutinizer ignore-unused */array $data = []
): string {
return $url;
}


Expand Down
10 changes: 5 additions & 5 deletions src/SAML2/HTTPArtifact.php
Expand Up @@ -5,6 +5,8 @@
namespace SimpleSAML\SAML2;

use Exception;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use SimpleSAML\Assert\Assert;
use SimpleSAML\Configuration;
Expand Down Expand Up @@ -91,15 +93,13 @@ public function getRedirectURL(AbstractMessage $message): string
/**
* Send a SAML 2 message using the HTTP-Redirect binding.
*
* Note: This function never returns.
*
* @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message we should send.
* @throws \Exception
* @return \Psr\Http\Message\ResponseInterface
*/
public function send(AbstractMessage $message): void
public function send(AbstractMessage $message): ResponseInterface
{
$destination = $this->getRedirectURL($message);
Utils::getContainer()->redirect($destination);
return new Response(303, ['Location' => $destination]);
}


Expand Down
10 changes: 6 additions & 4 deletions src/SAML2/HTTPPost.php
Expand Up @@ -7,6 +7,8 @@
use DOMDocument;
use DOMElement;
use Exception;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use SimpleSAML\SAML2\XML\samlp\AbstractMessage;
use SimpleSAML\SAML2\XML\samlp\AbstractRequest;
Expand All @@ -27,11 +29,10 @@ class HTTPPost extends Binding
/**
* Send a SAML 2 message using the HTTP-POST binding.
*
* Note: This function never returns.
*
* @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message we should send.
* @return \Psr\Http\Message\ResponseInterface The response
*/
public function send(AbstractMessage $message): void
public function send(AbstractMessage $message): ResponseInterface
{
if ($this->destination === null) {
$destination = $message->getDestination();
Expand Down Expand Up @@ -63,7 +64,8 @@ public function send(AbstractMessage $message): void
$post['RelayState'] = $relayState;
}

Utils::getContainer()->postRedirect($destination, $post);
$container = Utils::getContainer();
return new Response(303, ['Location' => $container->getPOSTRedirectURL($destination, $post)]);
}


Expand Down
14 changes: 9 additions & 5 deletions src/SAML2/HTTPRedirect.php
Expand Up @@ -6,6 +6,8 @@

use DOMElement;
use Exception;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML2\Compat\ContainerSingleton;
Expand Down Expand Up @@ -94,15 +96,17 @@ public function getRedirectURL(AbstractMessage $message): string

/**
* Send a SAML 2 message using the HTTP-Redirect binding.
* Note: This function never returns.
*
* @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message we should send.
* @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message
* @return \Psr\Http\Message\ResponseInterface
*/
public function send(AbstractMessage $message): void
public function send(AbstractMessage $message): ResponseInterface
{
$destination = $this->getRedirectURL($message);
Utils::getContainer()->getLogger()->debug('Redirect to ' . strlen($destination) . ' byte URL: ' . $destination);
Utils::getContainer()->redirect($destination);
Utils::getContainer()->getLogger()->debug(
'Redirect to ' . strlen($destination) . ' byte URL: ' . $destination
);
return new Response(303, ['Location' => $destination]);
}


Expand Down
39 changes: 19 additions & 20 deletions src/SAML2/SOAP.php
Expand Up @@ -6,19 +6,21 @@

use DOMDocument;
use Exception;
use SimpleSAML\SAML2\Exception\Protocol\UnsupportedBindingException;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use SimpleSAML\SOAP\Constants as C;
use SimpleSAML\SOAP11\XML\env\Body;
use SimpleSAML\SOAP11\XML\env\Envelope;
use SimpleSAML\SOAP11\XML\env\Header;
use SimpleSAML\SAML2\Exception\Protocol\UnsupportedBindingException;
use SimpleSAML\SAML2\Utils;
use SimpleSAML\SAML2\Utils\XPath;
use SimpleSAML\SAML2\XML\ecp\Response as ECPResponse;
use SimpleSAML\SAML2\XML\ecp\RequestAuthenticated;
use SimpleSAML\SAML2\XML\samlp\AbstractMessage;
use SimpleSAML\SAML2\XML\samlp\MessageFactory;
use SimpleSAML\SAML2\XML\samlp\Response;
use SimpleSAML\SAML2\XML\samlp\Response as SAML2_Response;
use SimpleSAML\SOAP\Constants as C;
use SimpleSAML\SOAP11\XML\env\Body;
use SimpleSAML\SOAP11\XML\env\Envelope;
use SimpleSAML\SOAP11\XML\env\Header;
use SimpleSAML\XML\DOMDocumentFactory;

use function file_get_contents;
Expand All @@ -44,7 +46,7 @@ public function getOutputToSend(AbstractMessage $message)
// In the Artifact Resolution profile, this will be an ArtifactResolve
// containing another message (e.g. a Response), however in the ECP
// profile, this is the Response itself.
if ($message instanceof Response) {
if ($message instanceof SAML2_Response) {
$requestAuthenticated = new RequestAuthenticated(1);

$destination = $this->destination ?: $message->getDestination();
Expand All @@ -66,32 +68,27 @@ public function getOutputToSend(AbstractMessage $message)
/**
* Send a SAML 2 message using the SOAP binding.
*
* Note: This function never returns.
*
* @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message we should send.
* @return \Psr\Http\Message\ResponseInterface
*/
public function send(AbstractMessage $message): void
public function send(AbstractMessage $message): ResponseInterface
{
header('Content-Type: text/xml', true);

$xml = $this->getOutputToSend($message);
if ($xml !== false) {
Utils::getContainer()->debugMessage($xml, 'out');
}
Utils::getContainer()->debugMessage($xml, 'out');

// DOMDocument::saveXML() returned false. Something is seriously wrong here. Not much we can do.
throw new Exception('Error while generating XML for SAML message.');
return new Response(200, ['Content-Type' => 'text/xml'], $xml);
}


/**
* Receive a SAML 2 message sent using the HTTP-POST binding.
*
* @param \Psr\Http\Message\ServerRequestInterface $request
* @return \SimpleSAML\SAML2\XML\samlp\AbstractMessage The received message.
* @return \SimpleSAML\SAML2\XML\samlp\AnstractMessage The received message.
*
* @throws \Exception If unable to receive the message
*/
public function receive(ServerRequestInterface $request): AbstractMessage
public function receive(/** @scrutinizer ignore-unused */ServerRequestInterface $request): AbstractMessage
{
$postText = $this->getInputStream();

Expand All @@ -103,8 +100,10 @@ public function receive(ServerRequestInterface $request): AbstractMessage
/** @var \DOMNode $xml */
$xml = $document->firstChild;
Utils::getContainer()->debugMessage($document->documentElement, 'in');

$xpCache = XPath::getXPath($document->documentElement);
/** @var \DOMElement[] $results */
$results = XPath::xpQuery($xml, '/soap-env:Envelope/soap-env:Body/*[1]', XPath::getXPath($xml));
$results = XPath::xpQuery($xml, '/soap-env:Envelope/soap-env:Body/*[1]', $xpCache);

return MessageFactory::fromXML($results[0]);
}
Expand Down

0 comments on commit 55799d8

Please sign in to comment.