Processor::enforcePreconditions()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\Response;
6
7
use Psr\Log\LoggerInterface;
8
use SimpleSAML\SAML2\Assertion\ProcessorBuilder;
9
use SimpleSAML\SAML2\Configuration\Destination;
10
use SimpleSAML\SAML2\Configuration\IdentityProvider;
11
use SimpleSAML\SAML2\Configuration\ServiceProvider;
12
use SimpleSAML\SAML2\Response\Exception\InvalidResponseException;
13
use SimpleSAML\SAML2\Response\Exception\NoAssertionsFoundException;
14
use SimpleSAML\SAML2\Response\Exception\PreconditionNotMetException;
15
use SimpleSAML\SAML2\Response\Exception\UnsignedResponseException;
16
use SimpleSAML\SAML2\Response\Validation\PreconditionValidator;
17
use SimpleSAML\SAML2\Signature\Validator;
18
use SimpleSAML\SAML2\Utilities\ArrayCollection;
19
use SimpleSAML\SAML2\XML\samlp\Response;
20
21
use function sprintf;
22
23
class Processor
24
{
25
    /**
26
     * @var \SimpleSAML\SAML2\Response\Validation\PreconditionValidator
27
     */
28
    private PreconditionValidator $preconditionValidator;
29
30
    /**
31
     * @var \SimpleSAML\SAML2\Signature\Validator
32
     */
33
    private Validator $signatureValidator;
34
35
    /**
36
     * @var \SimpleSAML\SAML2\Assertion\Processor
37
     */
38
    private $assertionProcessor;
39
40
    /**
41
     * Indicates whether or not the response was signed. This is required in order to be able to check whether either
42
     * the reponse or one of its assertions was signed
43
     */
44
    private bool $responseIsSigned = false;
45
46
47
    /**
48
     * @param \Psr\Log\LoggerInterface $logger
49
     */
50
    public function __construct(
51
        private LoggerInterface $logger,
52
    ) {
53
        $this->signatureValidator = new Validator($logger);
54
    }
55
56
57
    /**
58
     * @param \SimpleSAML\SAML2\Configuration\ServiceProvider  $serviceProviderConfiguration
59
     * @param \SimpleSAML\SAML2\Configuration\IdentityProvider $identityProviderConfiguration
60
     * @param \SimpleSAML\SAML2\Configuration\Destination $currentDestination
61
     * @param \SimpleSAML\SAML2\XML\samlp\Response $response
62
     *
63
     * @return \SimpleSAML\SAML2\Utilities\ArrayCollection Collection of \SimpleSAML\SAML2\XML\saml\Assertion objects
64
     */
65
    public function process(
66
        ServiceProvider $serviceProviderConfiguration,
67
        IdentityProvider $identityProviderConfiguration,
68
        Destination $currentDestination,
69
        Response $response,
70
    ): ArrayCollection {
71
        $this->preconditionValidator = new PreconditionValidator($currentDestination);
72
        $this->assertionProcessor = ProcessorBuilder::build(
73
            $this->logger,
74
            $this->signatureValidator,
75
            $currentDestination,
76
            $identityProviderConfiguration,
77
            $serviceProviderConfiguration,
78
            $response,
79
        );
80
81
        $this->enforcePreconditions($response);
82
        $this->verifySignature($response, $identityProviderConfiguration);
83
        return $this->processAssertions($response);
84
    }
85
86
87
    /**
88
     * Checks the preconditions that must be valid in order for the response to be processed.
89
     *
90
     * @param \SimpleSAML\SAML2\XML\samlp\Response $response
91
     *
92
     * @throws \SimpleSAML\SAML2\Response\Exception\PreconditionNotMetException
93
     */
94
    private function enforcePreconditions(Response $response): void
95
    {
96
        $result = $this->preconditionValidator->validate($response);
97
98
        if (!$result->isValid()) {
99
            throw PreconditionNotMetException::createFromValidationResult($result);
100
        }
101
    }
102
103
104
    /**
105
     * @param \SimpleSAML\SAML2\XML\samlp\Response $response
106
     * @param \SimpleSAML\SAML2\Configuration\IdentityProvider $identityProviderConfiguration
107
     *
108
     * @throws \SimpleSAML\SAML2\Response\Exception\InvalidResponseException
109
     */
110
    private function verifySignature(Response $response, IdentityProvider $identityProviderConfiguration): void
111
    {
112
        if (!$response->isMessageConstructedWithSignature()) {
113
            $this->logger->info(sprintf(
114
                'SAMLResponse with id "%s" was not signed at root level, not attempting to verify the signature of the'
115
                . ' reponse itself',
116
                $response->getId(),
117
            ));
118
119
            return;
120
        }
121
122
        $this->logger->info(sprintf(
123
            'Attempting to verify the signature of SAMLResponse with id "%s"',
124
            $response->getId(),
125
        ));
126
127
        $this->responseIsSigned = true;
128
129
        if (!$this->signatureValidator->hasValidSignature($response, $identityProviderConfiguration)) {
130
            throw new InvalidResponseException(
131
                sprintf('The SAMLResponse with id "%s", does not have a valid signature', $response->getId()),
132
            );
133
        }
134
    }
135
136
137
    /**
138
     * @param \SimpleSAML\SAML2\XML\samlp\Response $response
139
     * @return \SimpleSAML\SAML2\Utilities\ArrayCollection
140
     *
141
     * @throws \SimpleSAML\SAML2\Response\Exception\UnsignedResponseException
142
     * @throws \SimpleSAML\SAML2\Response\Exception\NoAssertionsFoundException
143
     */
144
    private function processAssertions(Response $response): ArrayCollection
145
    {
146
        $assertions = $response->getAssertions();
147
        if (empty($assertions)) {
148
            throw new NoAssertionsFoundException('No assertions found in response from IdP.');
149
        }
150
151
        $decryptedAssertions = $this->assertionProcessor->decryptAssertions(
152
            new ArrayCollection($assertions),
153
        );
154
155
        if (!$this->responseIsSigned) {
156
            foreach ($assertions as $assertion) {
157
                if (!$assertion->wasSignedAtConstruction()) {
158
                    throw new UnsignedResponseException(
159
                        'Both the response and the assertion it contains are not signed.',
160
                    );
161
                }
162
            }
163
        }
164
165
        return $this->assertionProcessor->processAssertions($decryptedAssertions);
166
    }
167
}
168