HTTPPost::send()   A
last analyzed

Complexity

Conditions 5
Paths 9

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 21
nc 9
nop 1
dl 0
loc 34
rs 9.2728
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\Binding;
6
7
use Exception;
8
use Nyholm\Psr7\Response;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use SimpleSAML\Assert\Assert;
12
use SimpleSAML\SAML2\Binding;
13
use SimpleSAML\SAML2\Binding\RelayStateTrait;
14
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
15
use SimpleSAML\SAML2\Utils;
16
use SimpleSAML\SAML2\XML\samlp\AbstractMessage;
17
use SimpleSAML\SAML2\XML\samlp\AbstractRequest;
18
use SimpleSAML\SAML2\XML\samlp\MessageFactory;
19
use SimpleSAML\XML\DOMDocumentFactory;
20
21
use function array_key_exists;
22
use function base64_decode;
23
use function base64_encode;
24
25
/**
26
 * Class which implements the HTTP-POST binding.
27
 *
28
 * @package simplesamlphp/saml2
29
 */
30
class HTTPPost extends Binding implements AsynchronousBindingInterface, RelayStateInterface
31
{
32
    use RelayStateTrait;
33
34
35
    /**
36
     * Send a SAML 2 message using the HTTP-POST binding.
37
     *
38
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message we should send.
39
     * @return \Psr\Http\Message\ResponseInterface The response
40
     */
41
    public function send(AbstractMessage $message): ResponseInterface
42
    {
43
        if ($this->destination === null) {
44
            $destination = $message->getDestination();
45
            if ($destination === null) {
46
                throw new Exception('Cannot send message, no destination set.');
47
            }
48
        } else {
49
            $destination = $this->destination;
50
        }
51
        $relayState = $this->getRelayState();
52
53
        $msgStr = $message->toXML();
54
55
        Utils::getContainer()->debugMessage($msgStr, 'out');
56
        $msgStr = $msgStr->ownerDocument?->saveXML($msgStr);
57
58
        $msgStr = base64_encode($msgStr);
59
60
        if ($message instanceof AbstractRequest) {
61
            $msgType = 'SAMLRequest';
62
        } else {
63
            $msgType = 'SAMLResponse';
64
        }
65
66
        $post = [];
67
        $post[$msgType] = $msgStr;
68
69
        if ($relayState !== null) {
70
            $post['RelayState'] = $relayState;
71
        }
72
73
        $container = Utils::getContainer();
74
        return new Response(303, ['Location' => $container->getPOSTRedirectURL($destination, $post)]);
75
    }
76
77
78
    /**
79
     * Receive a SAML 2 message sent using the HTTP-POST binding.
80
     *
81
     * Throws an exception if it is unable receive the message.
82
     *
83
     * @param \Psr\Http\Message\ServerRequestInterface $request
84
     * @return \SimpleSAML\SAML2\XML\samlp\AbstractMessage The received message.
85
     * @throws \Exception
86
     */
87
    public function receive(ServerRequestInterface $request): AbstractMessage
88
    {
89
        $query = $request->getParsedBody();
90
        if (array_key_exists('SAMLRequest', $query)) {
91
            $msgStr = $query['SAMLRequest'];
92
        } elseif (array_key_exists('SAMLResponse', $query)) {
93
            $msgStr = $query['SAMLResponse'];
94
        } else {
95
            throw new Exception('Missing SAMLRequest or SAMLResponse parameter.');
96
        }
97
98
        $msgStr = base64_decode($msgStr, true);
99
        $msgStr = DOMDocumentFactory::fromString($msgStr)->saveXML();
100
101
        $document = DOMDocumentFactory::fromString($msgStr);
102
        Utils::getContainer()->debugMessage($document->documentElement, 'in');
103
104
        $msg = MessageFactory::fromXML($document->documentElement);
105
106
        /**
107
         * 3.5.5.2 - SAML Bindings
108
         *
109
         * If the message is signed, the Destination XML attribute in the root SAML element of the protocol
110
         * message MUST contain the URL to which the sender has instructed the user agent to deliver the
111
         * message.
112
         */
113
        if ($msg->isSigned()) {
114
            Assert::notNull($msg->getDestination(), ProtocolViolationException::class);
115
            // Validation of the Destination must be done upstream
116
        }
117
118
        if (array_key_exists('RelayState', $query)) {
119
            $this->setRelayState($query['RelayState']);
120
        }
121
122
        return $msg;
123
    }
124
}
125