File: saml2-logout.php

package info (click to toggle)
simplesamlphp 1.16.3-1%2Bdeb10u2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 21,036 kB
  • sloc: php: 73,175; ansic: 875; sh: 83; perl: 82; xml: 52; makefile: 46
file content (140 lines) | stat: -rw-r--r-- 5,271 bytes parent folder | download
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
<?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);

$source = SimpleSAML_Auth_Source::getById($sourceId);
if ($source === null) {
    throw new Exception('Could not find authentication source with id ' . $sourceId);
}
if (!($source instanceof sspmod_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();

$idpEntityId = $message->getIssuer();
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.');
}

$spEntityId = $source->getEntityId();

$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $source->getIdPMetadata($idpEntityId);
$spMetadata = $source->getMetadata();

sspmod_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: ' . sspmod_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 = sspmod_saml_Message::getDecryptionKeys($idpMetadata, $spMetadata);
        } catch (Exception $e) {
            throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage());
        }

        $blacklist = sspmod_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();

    $numLoggedOut = sspmod_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 = sspmod_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.');
    }

    $dst = $idpMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(
        \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));
}