Passed
Pull Request — master (#22)
by Tim
20:50 queued 06:14
created

ADFS::generateAssertion()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 72
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 47
nc 10
nop 5
dl 0
loc 72
rs 7.9119
c 2
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\adfs\IdP;
6
7
use DateInterval;
8
use DateTimeImmutable;
9
use DateTimeZone;
10
use Exception;
11
use SimpleSAML\Configuration;
12
use SimpleSAML\Error;
13
use SimpleSAML\IdP;
14
use SimpleSAML\Logger;
15
use SimpleSAML\Metadata\MetaDataStorageHandler;
16
use SimpleSAML\Module;
17
use SimpleSAML\SAML11\XML\saml\Assertion;
18
use SimpleSAML\SAML11\XML\saml\Attribute;
19
use SimpleSAML\SAML11\XML\saml\AttributeStatement;
20
use SimpleSAML\SAML11\XML\saml\AttributeValue;
21
use SimpleSAML\SAML11\XML\saml\Audience;
22
use SimpleSAML\SAML11\XML\saml\AudienceRestrictionCondition;
23
use SimpleSAML\SAML11\XML\saml\AuthenticationStatement;
24
use SimpleSAML\SAML11\XML\saml\Conditions;
25
use SimpleSAML\SAML11\XML\saml\NameIdentifier;
26
use SimpleSAML\SAML11\XML\saml\Subject;
27
use SimpleSAML\SAML11\Constants as C;
28
use SimpleSAML\Utils;
29
use SimpleSAML\WSSecurity\XML\wsa_200508\Address;
30
use SimpleSAML\WSSecurity\XML\wsa_200508\EndpointReference;
31
use SimpleSAML\WSSecurity\XML\wsp\AppliesTo;
32
use SimpleSAML\WSSecurity\XML\wst_200502\RequestSecurityToken;
33
use SimpleSAML\WSSecurity\XML\wst_200502\RequestSecurityTokenResponse;
34
use SimpleSAML\XHTML\Template;
35
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
36
use SimpleSAML\XMLSecurity\Key\PrivateKey;
37
use SimpleSAML\XMLSecurity\Key\X509Certificate as PublicKey;
38
use SimpleSAML\XMLSecurity\XML\ds\KeyInfo;
39
use SimpleSAML\XMLSecurity\XML\ds\X509Certificate;
40
use SimpleSAML\XMLSecurity\XML\ds\X509Data;
41
use Symfony\Component\HttpFoundation\Request;
42
use Symfony\Component\HttpFoundation\StreamedResponse;
43
44
use function base64_encode;
45
use function chunk_split;
46
use function trim;
47
48
class ADFS
49
{
50
    /**
51
     * @param \SimpleSAML\IdP $idp
52
     * @throws \SimpleSAML\Error\MetadataNotFound
53
     */
54
    public static function receiveAuthnRequest(Request $request, IdP $idp): StreamedResponse
55
    {
56
        parse_str($request->server->get('QUERY_STRING'), $query);
57
58
        $requestid = $query['wctx'] ?? null;
59
        $issuer = $query['wtrealm'];
60
61
        $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance());
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\Metadata\Meta...r::getMetadataHandler() has too many arguments starting with SimpleSAML\Configuration::getInstance(). ( Ignorable by Annotation )

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

61
        /** @scrutinizer ignore-call */ 
62
        $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance());

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
62
        $spMetadata = $metadata->getMetaDataConfig($issuer, 'adfs-sp-remote');
63
64
        Logger::info('ADFS - IdP.prp: Incoming Authentication request: ' . $issuer . ' id ' . $requestid);
65
66
        $username = null;
67
        if ($request->query->has('username')) {
68
            $username = (string) $request->query->get('username');
69
        }
70
71
        $state = [
72
            'Responder' => [ADFS::class, 'sendResponse'],
73
            'SPMetadata' => $spMetadata->toArray(),
74
            'ForceAuthn' => false,
75
            'isPassive' => false,
76
            'adfs:wctx' => $requestid,
77
            'adfs:wreply' => false,
78
        ];
79
80
        if ($username !== null) {
81
            $state['core:username'] = $username;
82
        }
83
84
        if (isset($query['wreply']) && !empty($query['wreply'])) {
85
            $httpUtils = new Utils\HTTP();
86
            $state['adfs:wreply'] = $httpUtils->checkURLAllowed($query['wreply']);
87
        }
88
89
        return new StreamedResponse(
90
            function () use ($idp, &$state) {
91
                $idp->handleAuthenticationRequest($state);
92
            },
93
        );
94
    }
95
96
97
    /**
98
     * @param string $issuer
99
     * @param string $target
100
     * @param string $nameid
101
     * @param array $attributes
102
     * @param int $assertionLifetime
103
     * @return \SimpleSAML\SAML11\XML\saml\Assertion
104
     */
105
    private static function generateAssertion(
106
        string $issuer,
107
        string $target,
108
        string $nameid,
109
        array $attributes,
110
        int $assertionLifetime,
111
    ): Assertion {
112
        $httpUtils = new Utils\HTTP();
113
        $randomUtils = new Utils\Random();
114
        $timeUtils = new Utils\Time();
115
116
        $issueInstant = $timeUtils->generateTimestamp();
0 ignored issues
show
Unused Code introduced by
The assignment to $issueInstant is dead and can be removed.
Loading history...
117
        $notBefore = DateInterval::createFromDateString('30 seconds');
118
        $notOnOrAfter = DateInterval::createFromDateString(sprintf('%d seconds', $assertionLifetime));
119
        $assertionID = $randomUtils->generateID();
120
        $nameidFormat = 'http://schemas.xmlsoap.org/claims/UPN';
121
        $nameid = htmlspecialchars($nameid);
122
        $now = new DateTimeImmutable('now', new DateTimeZone('Z'));
123
124
        if ($httpUtils->isHTTPS()) {
125
            $method = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport';
126
        } else {
127
            $method = C::AC_PASSWORD;
128
        }
129
130
        $audience = new Audience($target);
131
        $audienceRestrictionCondition = new AudienceRestrictionCondition([$audience]);
132
        $conditions = new Conditions(
133
            [$audienceRestrictionCondition],
134
            [],
135
            [],
136
            $now->sub($notBefore),
137
            $now->add($notOnOrAfter),
138
        );
139
140
        $nameIdentifier = new NameIdentifier($nameid, null, $nameidFormat);
141
        $subject = new Subject(null, $nameIdentifier);
142
143
        $authenticationStatement = new AuthenticationStatement($subject, $method, $now);
144
145
        $attrs = [];
146
        $attrUtils = new Utils\Attributes();
147
        foreach ($attributes as $name => $values) {
148
            if ((!is_array($values)) || (count($values) == 0)) {
149
                continue;
150
            }
151
152
            list($namespace, $name) = $attrUtils->getAttributeNamespace(
153
                $name,
154
                'http://schemas.xmlsoap.org/claims',
155
            );
156
157
            $namespace = htmlspecialchars($namespace);
158
            $name = htmlspecialchars($name);
159
            $attrValue = [];
160
            foreach ($values as $value) {
161
                if ((!isset($value)) || ($value === '')) {
162
                    continue;
163
                }
164
                $attrValue[] = new AttributeValue($value);
165
            }
166
            $attrs[] = new Attribute($name, $namespace, $attrValue);
167
        }
168
        $attributeStatement = new AttributeStatement($subject, $attrs);
169
170
        return new Assertion(
171
            $assertionID,
172
            $issuer,
173
            $now,
174
            $conditions,
175
            null, // Advice
176
            [$authenticationStatement, $attributeStatement],
177
        );
178
    }
179
180
181
    /**
182
     * @param \SimpleSAML\SAML11\XML\saml\Assertion $assertion
183
     * @param string $key
184
     * @param string $cert
185
     * @param string $algo
186
     * @param string|null $passphrase
187
     * @return \SimpleSAML\SAML11\XML\saml\Assertion
188
     */
189
    private static function signAssertion(
190
        Assertion $assertion,
191
        string $key,
192
        string $cert,
193
        string $algo,
194
        #[\SensitiveParameter]
195
        string $passphrase = null,
196
    ): Assertion {
197
        $key = PrivateKey::fromFile($key, $passphrase);
0 ignored issues
show
Bug introduced by
It seems like $passphrase 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

197
        $key = PrivateKey::fromFile($key, /** @scrutinizer ignore-type */ $passphrase);
Loading history...
198
        $pubkey = PublicKey::fromFile($cert);
199
        $keyInfo = new KeyInfo([
200
            new X509Data(
201
                [new X509Certificate(
202
                    trim(chunk_split(base64_encode($pubkey->getPEM()->data()))),
203
                )],
204
            ),
205
        ]);
206
207
        $signer = (new SignatureAlgorithmFactory())->getAlgorithm(
208
            $algo,
209
            $key,
210
        );
211
212
        $assertion->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo);
213
        return $assertion;
214
    }
215
216
217
    /**
218
     * @param string $wreply
219
     * @param string $wresult
220
     * @param ?string $wctx
221
     */
222
    private static function postResponse(string $wreply, string $wresult, ?string $wctx): void
223
    {
224
        $config = Configuration::getInstance();
225
        $t = new Template($config, 'adfs:postResponse.twig');
226
        $t->data['wreply'] = $wreply;
227
        $t->data['wresult'] = $wresult;
228
        $t->data['wctx'] = $wctx;
229
        $t->send();
230
        // Idp->postAuthProc expects this function to exit
231
        exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
232
    }
233
234
235
    /**
236
     * Get the metadata of a given hosted ADFS IdP.
237
     *
238
     * @param string $entityid The entity ID of the hosted ADFS IdP whose metadata we want to fetch.
239
     * @param \SimpleSAML\Metadata\MetaDataStorageHandler $handler Optionally the metadata storage to use,
240
     *        if omitted the configured handler will be used.
241
     * @return array
242
     *
243
     * @throws \SimpleSAML\Error\Exception
244
     * @throws \SimpleSAML\Error\MetadataNotFound
245
     */
246
    public static function getHostedMetadata(string $entityid, MetaDataStorageHandler $handler = null): array
247
    {
248
        $cryptoUtils = new Utils\Crypto();
249
250
        $globalConfig = Configuration::getInstance();
251
        if ($handler === null) {
252
            $handler = MetaDataStorageHandler::getMetadataHandler($globalConfig);
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\Metadata\Meta...r::getMetadataHandler() has too many arguments starting with $globalConfig. ( Ignorable by Annotation )

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

252
            /** @scrutinizer ignore-call */ 
253
            $handler = MetaDataStorageHandler::getMetadataHandler($globalConfig);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
253
        }
254
        $config = $handler->getMetaDataConfig($entityid, 'adfs-idp-hosted');
255
256
        $host = Module::getModuleURL('adfs/idp/prp.php');
257
258
        // configure endpoints
259
        $ssob = $handler->getGenerated('SingleSignOnServiceBinding', 'adfs-idp-hosted', $host);
260
        $slob = $handler->getGenerated('SingleLogoutServiceBinding', 'adfs-idp-hosted', $host);
261
        $ssol = $handler->getGenerated('SingleSignOnService', 'adfs-idp-hosted', $host);
262
        $slol = $handler->getGenerated('SingleLogoutService', 'adfs-idp-hosted', $host);
263
264
        $sso = [];
265
        if (is_array($ssob)) {
266
            foreach ($ssob as $binding) {
267
                $sso[] = [
268
                    'Binding'  => $binding,
269
                    'Location' => $ssol,
270
                ];
271
            }
272
        } else {
273
            $sso[] = [
274
                'Binding'  => $ssob,
275
                'Location' => $ssol,
276
            ];
277
        }
278
279
        $slo = [];
280
        if (is_array($slob)) {
281
            foreach ($slob as $binding) {
282
                $slo[] = [
283
                    'Binding'  => $binding,
284
                    'Location' => $slol,
285
                ];
286
            }
287
        } else {
288
            $slo[] = [
289
                'Binding'  => $slob,
290
                'Location' => $slol,
291
            ];
292
        }
293
294
295
        $metadata = [
296
            'metadata-set' => 'adfs-idp-hosted',
297
            'entityid' => $entityid,
298
            'SingleSignOnService' => $sso,
299
            'SingleLogoutService' => $slo,
300
            'NameIDFormat' => $config->getOptionalArrayizeString('NameIDFormat', [C::NAMEID_TRANSIENT]),
0 ignored issues
show
Bug introduced by
The constant SimpleSAML\SAML11\Constants::NAMEID_TRANSIENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
301
            'contacts' => [],
302
        ];
303
304
        // add certificates
305
        $keys = [];
306
        $certInfo = $cryptoUtils->loadPublicKey($config, false, 'new_');
307
        $hasNewCert = false;
308
        if ($certInfo !== null) {
309
            $keys[] = [
310
                'type' => 'X509Certificate',
311
                'signing' => true,
312
                'encryption' => true,
313
                'X509Certificate' => $certInfo['certData'],
314
                'prefix' => 'new_',
315
            ];
316
            $hasNewCert = true;
317
        }
318
319
        /** @var array $certInfo */
320
        $certInfo = $cryptoUtils->loadPublicKey($config, true);
321
        $keys[] = [
322
            'type' => 'X509Certificate',
323
            'signing' => true,
324
            'encryption' => $hasNewCert === false,
325
            'X509Certificate' => $certInfo['certData'],
326
            'prefix' => '',
327
        ];
328
329
        if ($config->hasValue('https.certificate')) {
330
            /** @var array $httpsCert */
331
            $httpsCert = $cryptoUtils->loadPublicKey($config, true, 'https.');
332
            $keys[] = [
333
                'type' => 'X509Certificate',
334
                'signing' => true,
335
                'encryption' => false,
336
                'X509Certificate' => $httpsCert['certData'],
337
                'prefix' => 'https.',
338
            ];
339
        }
340
        $metadata['keys'] = $keys;
341
342
        // add organization information
343
        if ($config->hasValue('OrganizationName')) {
344
            $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName');
345
            $metadata['OrganizationDisplayName'] = $config->getOptionalLocalizedString(
346
                'OrganizationDisplayName',
347
                $metadata['OrganizationName'],
348
            );
349
350
            if (!$config->hasValue('OrganizationURL')) {
351
                throw new Error\Exception('If OrganizationName is set, OrganizationURL must also be set.');
352
            }
353
            $metadata['OrganizationURL'] = $config->getLocalizedString('OrganizationURL');
354
        }
355
356
        // add scope
357
        if ($config->hasValue('scope')) {
358
            $metadata['scope'] = $config->getArray('scope');
359
        }
360
361
        // add extensions
362
        if ($config->hasValue('EntityAttributes')) {
363
            $metadata['EntityAttributes'] = $config->getArray('EntityAttributes');
364
365
            // check for entity categories
366
            if (Utils\Config\Metadata::isHiddenFromDiscovery($metadata)) {
367
                $metadata['hide.from.discovery'] = true;
368
            }
369
        }
370
371
        if ($config->hasValue('UIInfo')) {
372
            $metadata['UIInfo'] = $config->getArray('UIInfo');
373
        }
374
375
        if ($config->hasValue('DiscoHints')) {
376
            $metadata['DiscoHints'] = $config->getArray('DiscoHints');
377
        }
378
379
        if ($config->hasValue('RegistrationInfo')) {
380
            $metadata['RegistrationInfo'] = $config->getArray('RegistrationInfo');
381
        }
382
383
        // add contact information
384
        $globalConfig = Configuration::getInstance();
385
        $email = $globalConfig->getOptionalString('technicalcontact_email', null);
386
        if ($email !== null && $email !== '[email protected]') {
387
            $contact = [
388
                'emailAddress' => $email,
389
                'givenName' => $globalConfig->getOptionalString('technicalcontact_name', null),
390
                'contactType' => 'technical',
391
            ];
392
            $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact);
393
        }
394
395
        return $metadata;
396
    }
397
398
399
    /**
400
     * @param array $state
401
     * @throws \Exception
402
     */
403
    public static function sendResponse(array $state): void
404
    {
405
        $spMetadata = $state["SPMetadata"];
406
        $spEntityId = $spMetadata['entityid'];
407
        $spMetadata = Configuration::loadFromArray(
408
            $spMetadata,
409
            '$metadata[' . var_export($spEntityId, true) . ']',
410
        );
411
412
        $attributes = $state['Attributes'];
413
414
        $nameidattribute = $spMetadata->getValue('simplesaml.nameidattribute');
415
        if (!empty($nameidattribute)) {
416
            if (!array_key_exists($nameidattribute, $attributes)) {
417
                throw new Exception('simplesaml.nameidattribute does not exist in resulting attribute set');
418
            }
419
            $nameid = $attributes[$nameidattribute][0];
420
        } else {
421
            $randomUtils = new Utils\Random();
422
            $nameid = $randomUtils->generateID();
423
        }
424
425
        $idp = IdP::getByState($state);
426
        $idpMetadata = $idp->getConfig();
427
        $idpEntityId = $idpMetadata->getString('entityid');
428
429
        $idp->addAssociation([
430
            'id' => 'adfs:' . $spEntityId,
431
            'Handler' => ADFS::class,
432
            'adfs:entityID' => $spEntityId,
433
        ]);
434
435
        $assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null);
436
        if ($assertionLifetime === null) {
437
            $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300);
438
        }
439
440
        $assertion = ADFS::generateAssertion($idpEntityId, $spEntityId, $nameid, $attributes, $assertionLifetime);
0 ignored issues
show
Bug introduced by
It seems like $assertionLifetime can also be of type null; however, parameter $assertionLifetime of SimpleSAML\Module\adfs\I...FS::generateAssertion() does only seem to accept integer, 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

440
        $assertion = ADFS::generateAssertion($idpEntityId, $spEntityId, $nameid, $attributes, /** @scrutinizer ignore-type */ $assertionLifetime);
Loading history...
441
442
        $configUtils = new Utils\Config();
443
        $privateKeyFile = $configUtils->getCertPath($idpMetadata->getString('privatekey'));
444
        $certificateFile = $configUtils->getCertPath($idpMetadata->getString('certificate'));
445
        $passphrase = $idpMetadata->getOptionalString('privatekey_pass', null);
446
447
        $algo = $spMetadata->getOptionalString('signature.algorithm', null);
448
        if ($algo === null) {
449
            $algo = $idpMetadata->getOptionalString('signature.algorithm', C::SIG_RSA_SHA256);
450
        }
451
        $assertion = ADFS::signAssertion($assertion, $privateKeyFile, $certificateFile, $algo, $passphrase);
0 ignored issues
show
Bug introduced by
It seems like $algo can also be of type null; however, parameter $algo of SimpleSAML\Module\adfs\IdP\ADFS::signAssertion() 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

451
        $assertion = ADFS::signAssertion($assertion, $privateKeyFile, $certificateFile, /** @scrutinizer ignore-type */ $algo, $passphrase);
Loading history...
452
453
        $requestSecurityToken = new RequestSecurityToken(null, [$assertion]);
454
        $appliesTo = new AppliesTo([new EndpointReference(new Address($spEntityId))]);
455
        $requestSecurityTokenResponse = new RequestSecurityTokenResponse(null, [$requestSecurityToken, $appliesTo]);
456
457
458
        $xmlResponse = $requestSecurityTokenResponse->toXML();
459
        $wresult = $xmlResponse->ownerDocument->saveXML($xmlResponse);
0 ignored issues
show
Bug introduced by
The method saveXML() 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

459
        /** @scrutinizer ignore-call */ 
460
        $wresult = $xmlResponse->ownerDocument->saveXML($xmlResponse);

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...
460
        $wctx = $state['adfs:wctx'];
461
        $wreply = $state['adfs:wreply'] ? : $spMetadata->getValue('prp');
462
        ADFS::postResponse($wreply, $wresult, $wctx);
463
    }
464
465
466
    /**
467
     * @param \SimpleSAML\IdP $idp
468
     * @param array $state
469
     */
470
    public static function sendLogoutResponse(IdP $idp, array $state): void
0 ignored issues
show
Unused Code introduced by
The parameter $state is not used and could be removed. ( Ignorable by Annotation )

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

470
    public static function sendLogoutResponse(IdP $idp, /** @scrutinizer ignore-unused */ array $state): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
471
    {
472
        // NB:: we don't know from which SP the logout request came from
473
        $idpMetadata = $idp->getConfig();
474
        $httpUtils = new Utils\HTTP();
475
        $httpUtils->redirectTrustedURL(
476
            $idpMetadata->getOptionalString('redirect-after-logout', $httpUtils->getBaseURL()),
0 ignored issues
show
Bug introduced by
It seems like $idpMetadata->getOptiona...ttpUtils->getBaseURL()) can also be of type null; however, parameter $url of SimpleSAML\Utils\HTTP::redirectTrustedURL() 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

476
            /** @scrutinizer ignore-type */ $idpMetadata->getOptionalString('redirect-after-logout', $httpUtils->getBaseURL()),
Loading history...
477
        );
478
    }
479
480
481
    /**
482
     * @param \SimpleSAML\IdP $idp
483
     * @throws \Exception
484
     */
485
    public static function receiveLogoutMessage(IdP $idp): void
486
    {
487
        // if a redirect is to occur based on wreply, we will redirect to url as
488
        // this implies an override to normal sp notification
489
        if (isset($_GET['wreply']) && !empty($_GET['wreply'])) {
490
            $httpUtils = new Utils\HTTP();
491
            $idp->doLogoutRedirect($httpUtils->checkURLAllowed($_GET['wreply']));
492
            throw new Exception("Code should never be reached");
493
        }
494
495
        $state = [
496
            'Responder' => [ADFS::class, 'sendLogoutResponse'],
497
        ];
498
        $assocId = null;
499
        // TODO: verify that this is really no problem for:
500
        //       a) SSP, because there's no caller SP.
501
        //       b) ADFS SP because caller will be called back..
502
        $idp->handleLogoutRequest($state, $assocId);
503
    }
504
505
506
    /**
507
     * accepts an association array, and returns a URL that can be accessed to terminate the association
508
     *
509
     * @param \SimpleSAML\IdP $idp
510
     * @param array $association
511
     * @param string $relayState
512
     * @return string
513
     */
514
    public static function getLogoutURL(IdP $idp, array $association, string $relayState): string
515
    {
516
        $metadata = MetaDataStorageHandler::getMetadataHandler();
517
        $spMetadata = $metadata->getMetaDataConfig($association['adfs:entityID'], 'adfs-sp-remote');
518
        $returnTo = Module::getModuleURL(
519
            'adfs/idp/prp.php?assocId=' . urlencode($association["id"]) . '&relayState=' . urlencode($relayState),
520
        );
521
        return $spMetadata->getValue('prp') . '?wa=wsignoutcleanup1.0&wreply=' . urlencode($returnTo);
522
    }
523
}
524