AttributeServer::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
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\ServerRequest;
9
use SimpleSAML\{Configuration, Error, Logger};
10
use SimpleSAML\HTTP\RunnableResponse;
11
use SimpleSAML\Metadata\MetaDataStorageHandler;
12
use SimpleSAML\SAML2\Binding\{SynchronousBindingInterface, SOAP};
13
use SimpleSAML\SAML2\Constants as C;
14
use SimpleSAML\SAML2\Utils as SAML2_Utils;
15
use SimpleSAML\SAML2\XML\saml\{
16
    Assertion,
17
    Attribute,
18
    AttributeStatement,
19
    AttributeValue,
20
    Audience,
21
    AudienceRestriction,
22
    Conditions,
23
    Issuer,
24
    Subject,
25
    SubjectConfirmation,
26
    SubjectConfirmationData,
27
};
28
use SimpleSAML\SAML2\XML\samlp\{AttributeQuery, Response, Status, StatusCode};
29
use SimpleSAML\Utils;
30
use SimpleSAML\XML\Exception\InvalidDOMElementException;
31
use SimpleSAML\XML\Utils\Random;
32
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
33
use SimpleSAML\XMLSecurity\Key\PrivateKey;
34
use SimpleSAML\XMLSecurity\XML\ds\{KeyInfo, X509Certificate, X509Data};
35
use SimpleSAML\XMLSecurity\XML\SignableElementInterface;
36
use Symfony\Bridge\PsrHttpMessage\Factory\{HttpFoundationFactory, PsrHttpFactory};
37
38
use function array_filter;
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 \Nyholm\Psr7\ServerRequest $request The current request.
80
     *
81
     * @return \SimpleSAML\HTTP\RunnableResponse
82
     * @throws \SimpleSAML\Error\BadRequest
83
     */
84
    public function main(/** @scrutinizer ignore-unused */ SOAP $soap, ServerRequest $request): RunnableResponse
85
    {
86
        $message = $soap->receive($request);
87
        Assert::isInstanceOf($message, AttributeQuery::class, InvalidDOMElement::class);
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\exampl...oller\InvalidDOMElement was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type SimpleSAML\Module\exampl...erver\Controller\Assert was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
88
89
        $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

89
        /** @scrutinizer ignore-call */ 
90
        $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...
90
91
        $issuer = $message->getIssuer();
92
        if ($issuer === null) {
93
            throw new Error\BadRequest('Missing <saml:Issuer> in <samlp:AttributeQuery>.');
94
        } else {
95
            $spEntityId = $issuer->getContent();
96
            if ($spEntityId === '') {
97
                throw new Error\BadRequest('Empty <saml:Issuer> in <samlp:AttributeQuery>.');
98
            }
99
        }
100
101
        $idpMetadata = $this->metadataHandler->getMetaDataConfig($idpEntityId, 'saml20-idp-hosted');
102
        $spMetadata = $this->metadataHandler->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
103
104
        // The endpoint we should deliver the message to
105
        $endpoint = $spMetadata->getString('testAttributeEndpoint');
106
107
        // The attributes we will return
108
        $attributes = [
109
            new Attribute(
110
                'name',
111
                C::NAMEFORMAT_UNSPECIFIED,
112
                null,
113
                [
114
                    new AttributeValue('value1'),
115
                    new AttributeValue('value2'),
116
                    new AttributeValue('value3'),
117
                ],
118
            ),
119
            new Attribute(
120
                'test',
121
                C::NAMEFORMAT_UNSPECIFIED,
122
                null,
123
                [
124
                    new AttributeValue('test'),
125
                ],
126
            ),
127
        ];
128
129
        // Determine which attributes we will return
130
        // @phpstan-ignore identical.alwaysFalse
131
        if (count($attributes) === 0) {
132
            Logger::debug('No attributes requested - return all attributes.');
133
            $attributeStatement = null;
134
        } else {
135
            $returnAttributes = [];
136
            foreach ($message->getAttributes() as $reqAttr) {
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on SimpleSAML\SAML2\XML\samlp\LogoutRequest. Did you maybe mean getAttribute()? ( Ignorable by Annotation )

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

136
            foreach ($message->/** @scrutinizer ignore-call */ getAttributes() as $reqAttr) {

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...
Bug introduced by
The method getAttributes() does not exist on SimpleSAML\SAML2\XML\samlp\AuthnRequest. Did you maybe mean getAttribute()? ( Ignorable by Annotation )

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

136
            foreach ($message->/** @scrutinizer ignore-call */ getAttributes() as $reqAttr) {

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...
Bug introduced by
The method getAttributes() does not exist on SimpleSAML\SAML2\XML\samlp\ArtifactResponse. Did you maybe mean getAttribute()? ( Ignorable by Annotation )

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

136
            foreach ($message->/** @scrutinizer ignore-call */ getAttributes() as $reqAttr) {

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...
Bug introduced by
The method getAttributes() does not exist on SimpleSAML\SAML2\XML\samlp\LogoutResponse. Did you maybe mean getAttribute()? ( Ignorable by Annotation )

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

136
            foreach ($message->/** @scrutinizer ignore-call */ getAttributes() as $reqAttr) {

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...
Bug introduced by
The method getAttributes() does not exist on SimpleSAML\SAML2\XML\samlp\Response. Did you maybe mean getAttribute()? ( Ignorable by Annotation )

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

136
            foreach ($message->/** @scrutinizer ignore-call */ getAttributes() as $reqAttr) {

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...
Bug introduced by
The method getAttributes() does not exist on SimpleSAML\SAML2\XML\samlp\ArtifactResolve. Did you maybe mean getAttribute()? ( Ignorable by Annotation )

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

136
            foreach ($message->/** @scrutinizer ignore-call */ getAttributes() as $reqAttr) {

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...
137
                foreach ($attributes as $attr) {
138
                    if (
139
                        $attr->getName() === $reqAttr->getName()
140
                        && $attr->getNameFormat() === $reqAttr->getNameFormat()
141
                    ) {
142
                        // The requested attribute is available
143
                        if ($reqAttr->getAttributeValues() === []) {
144
                            // If no specific values are requested, return all
145
                            $returnAttributes[] = $attr;
146
                        } else {
147
                            $returnValues = $this->filterAttributeValues(
148
                                $reqAttr->getAttributeValues(),
149
                                $attr->getAttributeValues(),
150
                            );
151
152
                            $returnAttributes[] = new Attribute(
153
                                $attr->getName(),
154
                                $attr->getNameFormat(),
155
                                null,
156
                                $returnValues,
157
                                $attr->getAttributesNS(),
158
                            );
159
                        }
160
                    }
161
                }
162
            }
163
164
            $attributeStatement = $returnAttributes ? (new AttributeStatement($returnAttributes)) : null;
165
        }
166
167
        // $returnAttributes contains the attributes we should return. Send them
168
        $clock = SAML2_Utils::getContainer()->getClock();
169
170
        $statements = array_filter([$attributeStatement]);
171
        $assertion = new Assertion(
172
            issuer: new Issuer($idpEntityId),
173
            issueInstant: $clock->now(),
174
            id: (new Random())->generateID(),
175
            subject: new Subject(
176
                identifier: $message->getSubject()->getIdentifier(),
0 ignored issues
show
Bug introduced by
The method getSubject() does not exist on SimpleSAML\SAML2\XML\samlp\ArtifactResolve. ( Ignorable by Annotation )

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

176
                identifier: $message->/** @scrutinizer ignore-call */ getSubject()->getIdentifier(),

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...
Bug introduced by
The method getSubject() does not exist on SimpleSAML\SAML2\XML\samlp\LogoutResponse. ( Ignorable by Annotation )

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

176
                identifier: $message->/** @scrutinizer ignore-call */ getSubject()->getIdentifier(),

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...
Bug introduced by
The method getSubject() does not exist on SimpleSAML\SAML2\XML\samlp\ArtifactResponse. ( Ignorable by Annotation )

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

176
                identifier: $message->/** @scrutinizer ignore-call */ getSubject()->getIdentifier(),

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...
Bug introduced by
The method getSubject() does not exist on SimpleSAML\SAML2\XML\samlp\LogoutRequest. ( Ignorable by Annotation )

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

176
                identifier: $message->/** @scrutinizer ignore-call */ getSubject()->getIdentifier(),

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...
Bug introduced by
The method getSubject() does not exist on SimpleSAML\SAML2\XML\samlp\Response. ( Ignorable by Annotation )

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

176
                identifier: $message->/** @scrutinizer ignore-call */ getSubject()->getIdentifier(),

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...
177
                subjectConfirmation: [
178
                    new SubjectConfirmation(
179
                        method: C::CM_BEARER,
180
                        subjectConfirmationData: new SubjectConfirmationData(
181
                            notOnOrAfter: $clock->now()->add(new DateInterval('PT300S')),
182
                            recipient: $endpoint,
183
                            inResponseTo: $message->getId(),
184
                        ),
185
                    ),
186
                ],
187
            ),
188
            conditions: new Conditions(
189
                notBefore: $clock->now(),
190
                notOnOrAfter: $clock->now()->add(new DateInterval('PT300S')),
191
                audienceRestriction: [
192
                    new AudienceRestriction([
193
                        new Audience($spEntityId),
194
                    ]),
195
                ],
196
            ),
197
            statements: $statements,
198
        );
199
200
        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

200
        /** @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...
201
202
        $response = new Response(
203
            status: new Status(
204
                new StatusCode(C::STATUS_SUCCESS),
205
            ),
206
            issueInstant: $clock->now(),
207
            issuer: $issuer,
208
            id: (new Random())->generateID(),
209
            version: '2.0',
210
            inResponseTo: $message->getId(),
211
            destination: $endpoint,
212
            assertions: [$assertion],
213
        );
214
215
        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

215
        /** @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...
216
217
        $soap = new SOAP();
218
        return new RunnableResponse([$soap, 'send'], [$response]);
219
    }
220
221
222
    /**
223
     * @param array<\SimpleSAML\SAML2\XML\saml\AttributeValue> $reqValues
224
     * @param array<\SimpleSAML\SAML2\XML\saml\AttributeValue> $values
225
     *
226
     * @return array<\SimpleSAML\SAML2\XML\saml\AttributeValue>
227
     */
228
    private function filterAttributeValues(array $reqValues, array $values): array
229
    {
230
        $result = [];
231
232
        foreach ($reqValues as $x) {
233
            foreach ($values as $y) {
234
                if ($x->getValue() === $y->getValue()) {
235
                    $result[] = $y;
236
                }
237
            }
238
        }
239
240
        return $result;
241
    }
242
243
244
    /**
245
     * @deprecated This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and
246
     *  should be replaced with a call to a future ServiceProvider-class in the saml2-library
247
     *
248
     * Add signature key and sender certificate to an element (Message or Assertion).
249
     *
250
     * @param \SimpleSAML\Configuration $srcMetadata The metadata of the sender.
251
     * @param \SimpleSAML\Configuration $dstMetadata The metadata of the recipient.
252
     * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $element The element we should add the data to.
253
     */
254
    private static function addSign(
255
        Configuration $srcMetadata,
256
        Configuration $dstMetadata,
257
        SignableElementInterface &$element,
258
    ): void {
259
        $dstPrivateKey = $dstMetadata->getOptionalString('signature.privatekey', null);
260
        $cryptoUtils = new Utils\Crypto();
261
262
        if ($dstPrivateKey !== null) {
263
            /** @var string[] $keyArray */
264
            $keyArray = $cryptoUtils->loadPrivateKey($dstMetadata, true, 'signature.');
265
            $certArray = $cryptoUtils->loadPublicKey($dstMetadata, false, 'signature.');
266
        } else {
267
            /** @var string[] $keyArray */
268
            $keyArray = $cryptoUtils->loadPrivateKey($srcMetadata, true);
269
            $certArray = $cryptoUtils->loadPublicKey($srcMetadata, false);
270
        }
271
272
        $algo = $dstMetadata->getOptionalString('signature.algorithm', null);
273
        if ($algo === null) {
274
            $algo = $srcMetadata->getOptionalString('signature.algorithm', C::SIG_RSA_SHA256);
275
        }
276
277
        $privateKey = PrivateKey::fromFile($keyArray['PEM'], $keyArray['password']);
278
279
        $keyInfo = null;
280
        if ($certArray !== null) {
281
            $keyInfo = new KeyInfo([
282
                new X509Data(
283
                    [
284
                        new X509Certificate($certArray['PEM']),
285
                    ],
286
                ),
287
            ]);
288
        }
289
290
        $signer = (new SignatureAlgorithmFactory())->getAlgorithm(
291
            $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

291
            /** @scrutinizer ignore-type */ $algo,
Loading history...
292
            $privateKey,
293
        );
294
295
        $element->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo);
296
    }
297
}
298