File: saml2-logout.php

package info (click to toggle)
simplesamlphp 1.19.7-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 42,920 kB
  • sloc: php: 202,044; javascript: 14,867; xml: 2,700; sh: 225; perl: 82; makefile: 70; python: 5
file content (152 lines) | stat: -rw-r--r-- 5,776 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<?php

/**
 * Logout endpoint handler for SAML SP authentication client.
 *
 * This endpoint handles both logout requests and logout responses.
 */

if (!array_key_exists('PATH_INFO', $_SERVER)) {
    throw new \SimpleSAML\Error\BadRequest('Missing authentication source ID in logout URL');
}

$sourceId = substr($_SERVER['PATH_INFO'], 1);

/** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */
$source = \SimpleSAML\Auth\Source::getById($sourceId);
if ($source === null) {
    throw new \Exception('Could not find authentication source with id ' . $sourceId);
} elseif (!($source instanceof \SimpleSAML\Module\saml\Auth\Source\SP)) {
    throw new \SimpleSAML\Error\Exception('Source type changed?');
}

try {
    $binding = \SAML2\Binding::getCurrentBinding();
} catch (\Exception $e) {
    // TODO: look for a specific exception
    // This is dirty. Instead of checking the message of the exception, \SAML2\Binding::getCurrentBinding() should throw
    // an specific exception when the binding is unknown, and we should capture that here
    if ($e->getMessage() === 'Unable to find the current binding.') {
        throw new \SimpleSAML\Error\Error('SLOSERVICEPARAMS', $e, 400);
    } else {
        throw $e; // do not ignore other exceptions!
    }
}
$message = $binding->receive();

$issuer = $message->getIssuer();
if ($issuer instanceof \SAML2\XML\saml\Issuer) {
    $idpEntityId = $issuer->getValue();
} else {
    $idpEntityId = $issuer;
}

if ($idpEntityId === null) {
    // Without an issuer we have no way to respond to the message.
    throw new \SimpleSAML\Error\BadRequest('Received message on logout endpoint without issuer.');
}

/** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */
$spEntityId = $source->getEntityId();

$metadata = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $source->getIdPMetadata($idpEntityId);
$spMetadata = $source->getMetadata();

\SimpleSAML\Module\saml\Message::validateMessage($idpMetadata, $spMetadata, $message);

$destination = $message->getDestination();
if ($destination !== null && $destination !== \SimpleSAML\Utils\HTTP::getSelfURLNoQuery()) {
    throw new \SimpleSAML\Error\Exception('Destination in logout message is wrong.');
}

if ($message instanceof \SAML2\LogoutResponse) {
    $relayState = $message->getRelayState();
    if ($relayState === null) {
        // Somehow, our RelayState has been lost.
        throw new \SimpleSAML\Error\BadRequest('Missing RelayState in logout response.');
    }

    if (!$message->isSuccess()) {
        \SimpleSAML\Logger::warning(
            'Unsuccessful logout. Status was: ' . \SimpleSAML\Module\saml\Message::getResponseError($message)
        );
    }

    $state = \SimpleSAML\Auth\State::loadState($relayState, 'saml:slosent');
    $state['saml:sp:LogoutStatus'] = $message->getStatus();
    \SimpleSAML\Auth\Source::completeLogout($state);
} elseif ($message instanceof \SAML2\LogoutRequest) {
    \SimpleSAML\Logger::debug('module/saml2/sp/logout: Request from ' . $idpEntityId);
    \SimpleSAML\Logger::stats('saml20-idp-SLO idpinit ' . $spEntityId . ' ' . $idpEntityId);

    if ($message->isNameIdEncrypted()) {
        try {
            $keys = \SimpleSAML\Module\saml\Message::getDecryptionKeys($idpMetadata, $spMetadata);
        } catch (\Exception $e) {
            throw new \SimpleSAML\Error\Exception('Error decrypting NameID: ' . $e->getMessage());
        }

        $blacklist = \SimpleSAML\Module\saml\Message::getBlacklistedAlgorithms($idpMetadata, $spMetadata);

        $lastException = null;
        foreach ($keys as $i => $key) {
            try {
                $message->decryptNameId($key, $blacklist);
                \SimpleSAML\Logger::debug('Decryption with key #' . $i . ' succeeded.');
                $lastException = null;
                break;
            } catch (\Exception $e) {
                \SimpleSAML\Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage());
                $lastException = $e;
            }
        }
        if ($lastException !== null) {
            throw $lastException;
        }
    }

    $nameId = $message->getNameId();
    $sessionIndexes = $message->getSessionIndexes();

    /** @psalm-suppress PossiblyNullArgument  This will be fixed in saml2 5.0 */
    $numLoggedOut = \SimpleSAML\Module\saml\SP\LogoutStore::logoutSessions($sourceId, $nameId, $sessionIndexes);
    if ($numLoggedOut === false) {
        // This type of logout was unsupported. Use the old method
        $source->handleLogout($idpEntityId);
        $numLoggedOut = count($sessionIndexes);
    }

    // Create and send response
    $lr = \SimpleSAML\Module\saml\Message::buildLogoutResponse($spMetadata, $idpMetadata);
    $lr->setRelayState($message->getRelayState());
    $lr->setInResponseTo($message->getId());

    if ($numLoggedOut < count($sessionIndexes)) {
        \SimpleSAML\Logger::warning('Logged out of ' . $numLoggedOut . ' of ' . count($sessionIndexes) . ' sessions.');
    }

    /** @var array $dst */
    $dst = $idpMetadata->getEndpointPrioritizedByBinding(
        'SingleLogoutService',
        [
            \SAML2\Constants::BINDING_HTTP_REDIRECT,
            \SAML2\Constants::BINDING_HTTP_POST
        ]
    );

    if (!($binding instanceof \SAML2\SOAP)) {
        $binding = \SAML2\Binding::getBinding($dst['Binding']);
        if (isset($dst['ResponseLocation'])) {
            $dst = $dst['ResponseLocation'];
        } else {
            $dst = $dst['Location'];
        }
        $binding->setDestination($dst);
    }
    $lr->setDestination($dst);

    $binding->send($lr);
} else {
    throw new \SimpleSAML\Error\BadRequest('Unknown message received on logout endpoint: ' . get_class($message));
}