Passed
Pull Request — master (#9)
by Tim
02:34
created

AttributeServer::filterAttributeValues()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 13
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\exampleattributeserver\Controller;
6
7
use DateInterval;
8
use Nyholm\Psr7\Factory\Psr17Factory;
9
use SimpleSAML\{Configuration, Error, Logger};
10
use SimpleSAML\HTTP\RunnableResponse;
11
use SimpleSAML\Metadata\MetaDataStorageHandler;
12
use SimpleSAML\SAML2\Binding;
13
use SimpleSAML\SAML2\Binding\HTTPPost;
14
use SimpleSAML\SAML2\Constants as C;
15
use SimpleSAML\SAML2\Utils as SAML2_Utils;
16
use SimpleSAML\SAML2\XML\saml\{
17
    Assertion,
18
    Attribute,
19
    AttributeStatement,
20
    AttributeValue,
21
    Audience,
22
    AudienceRestriction,
23
    Conditions,
24
    Issuer,
25
    Subject,
26
    SubjectConfirmation,
27
    SubjectConfirmationData,
28
};
29
use SimpleSAML\SAML2\XML\samlp\{AttributeQuery, Response, Status, StatusCode};
30
use SimpleSAML\Utils;
31
use SimpleSAML\XML\Utils\Random;
32
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
33
use SimpleSAML\XMLSecurity\CryptoEncoding\PEM;
34
use SimpleSAML\XMLSecurity\Key\PrivateKey;
35
use SimpleSAML\XMLSecurity\XML\ds\{KeyInfo, X509Certificate, X509Data};
36
use SimpleSAML\XMLSecurity\XML\SignableElementInterface;
37
use Symfony\Bridge\PsrHttpMessage\Factory\{HttpFoundationFactory, PsrHttpFactory};
38
use Symfony\Component\HttpFoundation\Request;
39
40
/**
41
 * Controller class for the exampleattributeserver module.
42
 *
43
 * This class serves the attribute server available in the module.
44
 *
45
 * @package SimpleSAML\Module\exampleattributeserver
46
 */
47
class AttributeServer
48
{
49
    /** @var \SimpleSAML\Configuration */
50
    protected Configuration $config;
51
52
    /** @var \SimpleSAML\Metadata\MetaDataStorageHandler|null */
53
    protected ?MetaDataStorageHandler $metadataHandler = null;
54
55
56
    /**
57
     * ConfigController constructor.
58
     *
59
     * @param \SimpleSAML\Configuration $config The configuration to use.
60
     */
61
    public function __construct(Configuration $config)
62
    {
63
        $this->config = $config;
64
    }
65
66
67
    /**
68
     * Inject the \SimpleSAML\Metadata\MetaDataStorageHandler dependency.
69
     *
70
     * @param \SimpleSAML\Metadata\MetaDataStorageHandler $handler
71
     */
72
    public function setMetadataStorageHandler(MetaDataStorageHandler $handler): void
73
    {
74
        $this->metadataHandler = $handler;
75
    }
76
77
78
    /**
79
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
80
     *
81
     * @return \SimpleSAML\HTTP\RunnableResponse
82
     * @throws \SimpleSAML\Error\BadRequest
83
     */
84
    public function main(/** @scrutinizer ignore-unused */ Request $request): RunnableResponse
85
    {
86
        $psr17Factory = new Psr17Factory();
87
        $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
88
        $psrRequest = $psrHttpFactory->createRequest($request);
89
90
        $binding = Binding::getCurrentBinding($psrRequest);
91
        $message = $binding->receive($psrRequest);
92
        if (!($message instanceof AttributeQuery)) {
93
            throw new Error\BadRequest('Invalid message received to AttributeQuery endpoint.');
94
        }
95
96
        $idpEntityId = $this->metadataHandler->getMetaDataCurrentEntityID('saml20-idp-hosted');
0 ignored issues
show
Bug introduced by
The method getMetaDataCurrentEntityID() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

96
        /** @scrutinizer ignore-call */ 
97
        $idpEntityId = $this->metadataHandler->getMetaDataCurrentEntityID('saml20-idp-hosted');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
97
98
        $issuer = $message->getIssuer();
99
        if ($issuer === null) {
100
            throw new Error\BadRequest('Missing <saml:Issuer> in <samlp:AttributeQuery>.');
101
        } else {
102
            $spEntityId = $issuer->getContent();
103
            if ($spEntityId === '') {
104
                throw new Error\BadRequest('Empty <saml:Issuer> in <samlp:AttributeQuery>.');
105
            }
106
        }
107
108
        $idpMetadata = $this->metadataHandler->getMetaDataConfig($idpEntityId, 'saml20-idp-hosted');
109
        $spMetadata = $this->metadataHandler->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
110
111
        // The endpoint we should deliver the message to
112
        $endpoint = $spMetadata->getString('testAttributeEndpoint');
113
114
        // The attributes we will return
115
        $attributes = [
116
            new Attribute(
117
                'name',
118
                C::NAMEFORMAT_UNSPECIFIED,
119
                null,
120
                [
121
                    new AttributeValue('value1'),
122
                    new AttributeValue('value2'),
123
                    new AttributeValue('value3'),
124
                ],
125
            ),
126
            new Attribute(
127
                'test',
128
                C::NAMEFORMAT_UNSPECIFIED,
129
                null,
130
                [
131
                    new AttributeValue('test'),
132
                ],
133
            ),
134
        ];
135
136
        // Determine which attributes we will return
137
        $returnAttributes = [];
138
139
        if (count($returnAttributes) === 0) {
140
            Logger::debug('No attributes requested - return all attributes.');
141
            $returnAttributes = $attributes;
142
        } else {
143
            foreach ($message->getAttributes() as $reqAttr) {
144
                foreach ($attributes as $attr) {
145
                    if (
146
                        $attr->getName() === $reqAttr->getName()
147
                        && $attr->getNameFormat() === $reqAttr->getNameFormat()
148
                    ) {
149
                        // The requested attribute is available
150
                        if ($reqAttr->getAttributeValues() === []) {
151
                            // If no specific values are requested, return all
152
                            $returnAttributes[] = $attr;
153
                        } else {
154
                            $returnValues = $this->filterAttributeValues(
155
                                $reqAttr->getAttributeValues(),
156
                                $attr->getAttributeValues(),
157
                            );
158
159
                            $returnAttributes[] = new Attribute(
160
                                $attr->getName(),
161
                                $attr->getNameFormat(),
162
                                $returnValues,
0 ignored issues
show
Bug introduced by
$returnValues of type SimpleSAML\SAML2\XML\saml\AttributeValue[]|array is incompatible with the type null|string expected by parameter $friendlyName of SimpleSAML\SAML2\XML\saml\Attribute::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
                                /** @scrutinizer ignore-type */ $returnValues,
Loading history...
163
                                $attr->getAttributesNS(),
164
                            );
165
                        }
166
                    }
167
                }
168
            }
169
        }
170
171
        // $returnAttributes contains the attributes we should return. Send them
172
        $clock = SAML2_Utils::getContainer()->getClock();
173
174
        $assertion = new Assertion(
175
            issuer: new Issuer($idpEntityId),
176
            issueInstant: $clock->now(),
177
            id: (new Random())->generateID(),
178
            subject: new Subject(
179
                identifier: $message->getSubject()->getIdentifier(),
180
                subjectConfirmation: [
181
                    new SubjectConfirmation(
182
                        method: C::CM_BEARER,
183
                        subjectConfirmationData: new SubjectConfirmationData(
184
                            notOnOrAfter: $clock->now()->add(new DateInterval('PT300S')),
185
                            recipient: $endpoint,
186
                            inResponseTo: $message->getId(),
187
                        ),
188
                    ),
189
                ],
190
            ),
191
            conditions: new Conditions(
192
                notBefore: $clock->now(),
193
                notOnOrAfter: $clock->now()->add(new DateInterval('PT300S')),
194
                audienceRestriction: [
195
                    new AudienceRestriction([
196
                        new Audience($spEntityId),
197
                    ]),
198
                ],
199
            ),
200
            statements: [
201
                new AttributeStatement($returnAttributes),
202
            ],
203
        );
204
205
        self::addSign($idpMetadata, $spMetadata, $assertion);
0 ignored issues
show
Deprecated Code introduced by
The function SimpleSAML\Module\exampl...ributeServer::addSign() has been deprecated: This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and should be replaced with a call to a future ServiceProvider-class in the saml2-library ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

205
        /** @scrutinizer ignore-deprecated */ self::addSign($idpMetadata, $spMetadata, $assertion);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
206
207
        $response = new Response(
208
            status: new Status(
209
                new StatusCode(C::STATUS_SUCCESS),
210
            ),
211
            issueInstant: $clock->now(),
212
            issuer: $issuer,
213
            id: (new Random())->generateID(),
214
            version: '2.0',
215
            inResponseTo: $message->getId(),
216
            destination: $endpoint,
217
            assertions: [$assertion],
218
        );
219
220
        self::addSign($idpMetadata, $spMetadata, $response);
0 ignored issues
show
Deprecated Code introduced by
The function SimpleSAML\Module\exampl...ributeServer::addSign() has been deprecated: This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and should be replaced with a call to a future ServiceProvider-class in the saml2-library ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

220
        /** @scrutinizer ignore-deprecated */ self::addSign($idpMetadata, $spMetadata, $response);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
221
222
        $httpPost = new HTTPPost();
223
        $httpPost->setRelayState($binding->getRelayState());
0 ignored issues
show
Bug introduced by
The method getRelayState() does not exist on SimpleSAML\SAML2\Binding\SOAP. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

223
        $httpPost->setRelayState($binding->/** @scrutinizer ignore-call */ getRelayState());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
224
225
        return new RunnableResponse([$httpPost, 'send'], [$response]);
226
    }
227
228
229
    /**
230
     * @param array<\SimpleSAML\SAML2\XML\saml\AttributeValue> $reqValues
231
     * @param array<\SimpleSAML\SAML2\XML\saml\AttributeValue> $values
232
     *
233
     * @return array<\SimpleSAML\SAML2\XML\saml\AttributeValue>
234
     */
235
    private function filterAttributeValues(array $reqValues, array $values): array
236
    {
237
        $result = [];
238
239
        foreach ($reqValues as $x) {
240
            foreach ($values as $y) {
241
                if ($x->getValue() === $y->getValue()) {
242
                    $result[] = $y;
243
                }
244
            }
245
        }
246
247
        return $result;
248
    }
249
250
251
    /**
252
     * @deprecated This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and
253
     *  should be replaced with a call to a future ServiceProvider-class in the saml2-library
254
     *
255
     * Add signature key and sender certificate to an element (Message or Assertion).
256
     *
257
     * @param \SimpleSAML\Configuration $srcMetadata The metadata of the sender.
258
     * @param \SimpleSAML\Configuration $dstMetadata The metadata of the recipient.
259
     * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $element The element we should add the data to.
260
     */
261
    private static function addSign(
262
        Configuration $srcMetadata,
263
        Configuration $dstMetadata,
264
        SignableElementInterface &$element,
265
    ): void {
266
        $dstPrivateKey = $dstMetadata->getOptionalString('signature.privatekey', null);
267
        $cryptoUtils = new Utils\Crypto();
268
269
        if ($dstPrivateKey !== null) {
270
            /** @var array $keyArray */
271
            $keyArray = $cryptoUtils->loadPrivateKey($dstMetadata, true, 'signature.');
272
            $certArray = $cryptoUtils->loadPublicKey($dstMetadata, false, 'signature.');
273
        } else {
274
            /** @var array $keyArray */
275
            $keyArray = $cryptoUtils->loadPrivateKey($srcMetadata, true);
276
            $certArray = $cryptoUtils->loadPublicKey($srcMetadata, false);
277
        }
278
279
        $algo = $dstMetadata->getOptionalString('signature.algorithm', null);
280
        if ($algo === null) {
281
            $algo = $srcMetadata->getOptionalString('signature.algorithm', C::SIG_RSA_SHA256);
282
        }
283
284
        $privateKey = PrivateKey::fromFile($keyArray['PEM'], $keyArray['password']);
0 ignored issues
show
Bug introduced by
It seems like $keyArray['password'] can also be of type null; however, parameter $passphrase of SimpleSAML\XMLSecurity\Key\PrivateKey::fromFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

284
        $privateKey = PrivateKey::fromFile($keyArray['PEM'], /** @scrutinizer ignore-type */ $keyArray['password']);
Loading history...
285
286
        $keyInfo = null;
287
        if ($certArray !== null) {
288
            $certificate = new X509Certificate(PEM::fromString($keyArray['PEM']));
0 ignored issues
show
Unused Code introduced by
The assignment to $certificate is dead and can be removed.
Loading history...
289
            $keyInfo = new KeyInfo([
290
                new X509Data(
291
                    [
292
                        new X509Certificate($certArray['PEM']),
293
                    ],
294
                ),
295
            ]);
296
        }
297
298
        $signer = (new SignatureAlgorithmFactory())->getAlgorithm(
299
            $algo,
0 ignored issues
show
Bug introduced by
It seems like $algo can also be of type null; however, parameter $algId of SimpleSAML\XMLSecurity\A...Factory::getAlgorithm() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
            /** @scrutinizer ignore-type */ $algo,
Loading history...
300
            $privateKey,
301
        );
302
303
        $element->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo);
304
    }
305
}
306