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
     * @var bool
45
     */
46
    private bool $responseIsSigned = false;
47
48
49
    /**
50
     * @param \Psr\Log\LoggerInterface $logger
51
     */
52
    public function __construct(
53
        private LoggerInterface $logger,
54
    ) {
55
        $this->signatureValidator = new Validator($logger);
56
    }
57
58
59
    /**
60
     * @param \SimpleSAML\SAML2\Configuration\ServiceProvider  $serviceProviderConfiguration
61
     * @param \SimpleSAML\SAML2\Configuration\IdentityProvider $identityProviderConfiguration
62
     * @param \SimpleSAML\SAML2\Configuration\Destination $currentDestination
63
     * @param \SimpleSAML\SAML2\XML\samlp\Response $response
64
     *
65
     * @return \SimpleSAML\SAML2\Utilities\ArrayCollection Collection of \SimpleSAML\SAML2\XML\saml\Assertion objects
66
     */
67
    public function process(
68
        ServiceProvider $serviceProviderConfiguration,
69
        IdentityProvider $identityProviderConfiguration,
70
        Destination $currentDestination,
71
        Response $response,
72
    ): ArrayCollection {
73
        $this->preconditionValidator = new PreconditionValidator($currentDestination);
74
        $this->assertionProcessor = ProcessorBuilder::build(
75
            $this->logger,
76
            $this->signatureValidator,
77
            $currentDestination,
78
            $identityProviderConfiguration,
79
            $serviceProviderConfiguration,
80
            $response,
81
        );
82
83
        $this->enforcePreconditions($response);
84
        $this->verifySignature($response, $identityProviderConfiguration);
85
        return $this->processAssertions($response);
86
    }
87
88
89
    /**
90
     * Checks the preconditions that must be valid in order for the response to be processed.
91
     *
92
     * @param \SimpleSAML\SAML2\XML\samlp\Response $response
93
     * @throws \SimpleSAML\SAML2\Response\Exception\PreconditionNotMetException
94
     */
95
    private function enforcePreconditions(Response $response): void
96
    {
97
        $result = $this->preconditionValidator->validate($response);
98
99
        if (!$result->isValid()) {
100
            throw PreconditionNotMetException::createFromValidationResult($result);
101
        }
102
    }
103
104
105
    /**
106
     * @param \SimpleSAML\SAML2\XML\samlp\Response $response
107
     * @param \SimpleSAML\SAML2\Configuration\IdentityProvider $identityProviderConfiguration
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
     * @throws \SimpleSAML\SAML2\Response\Exception\UnsignedResponseException
140
     * @throws \SimpleSAML\SAML2\Response\Exception\NoAssertionsFoundException
141
     * @return \SimpleSAML\SAML2\Utilities\ArrayCollection
142
     */
143
    private function processAssertions(Response $response): ArrayCollection
144
    {
145
        $assertions = $response->getAssertions();
146
        if (empty($assertions)) {
147
            throw new NoAssertionsFoundException('No assertions found in response from IdP.');
148
        }
149
150
        $decryptedAssertions = $this->assertionProcessor->decryptAssertions(
151
            new ArrayCollection($assertions),
152
        );
153
154
        if (!$this->responseIsSigned) {
155
            foreach ($assertions as $assertion) {
156
                if (!$assertion->wasSignedAtConstruction()) {
157
                    throw new UnsignedResponseException(
158
                        'Both the response and the assertion it contains are not signed.',
159
                    );
160
                }
161
            }
162
        }
163
164
        return $this->assertionProcessor->processAssertions($decryptedAssertions);
165
    }
166
}
167