{"action":"created","issue":{"url":"https://api.github.com/repos/aacotroneo/laravel-saml2/issues/12","labels_url":"https://api.github.com/repos/aacotroneo/laravel-saml2/issues/12/labels{/name}","comments_url":"https://api.github.com/repos/aacotroneo/laravel-saml2/issues/12/comments","events_url":"https://api.github.com/repos/aacotroneo/laravel-saml2/issues/12/events","html_url":"https://github.com/aacotroneo/laravel-saml2/issues/12","id":105383202,"number":12,"title":"Saml2Controller->sls() doesn't fire Saml2LogoutEvent if application didn't initiate logout","user":{"login":"samlev","id":462466,"avatar_url":"https://avatars.githubusercontent.com/u/462466?v=3","gravatar_id":"","url":"https://api.github.com/users/samlev","html_url":"https://github.com/samlev","followers_url":"https://api.github.com/users/samlev/followers","following_url":"https://api.github.com/users/samlev/following{/other_user}","gists_url":"https://api.github.com/users/samlev/gists{/gist_id}","starred_url":"https://api.github.com/users/samlev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/samlev/subscriptions","organizations_url":"https://api.github.com/users/samlev/orgs","repos_url":"https://api.github.com/users/samlev/repos","events_url":"https://api.github.com/users/samlev/events{/privacy}","received_events_url":"https://api.github.com/users/samlev/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2015-09-08T13:43:35Z","updated_at":"2015-09-08T14:37:16Z","closed_at":null,"body":"When the current application doesn't fire the logout event, the application receives `$_GET['SAMLRequest']` instead of `$_GET['SAMLResponse']`, which sends the OneLogin Saml2 library into a branch which redirects the user rather than returning:\r\n\r\n```php\r\nelse if (isset($_GET) && isset($_GET['SAMLRequest'])) {\r\n    if (!$logoutRequest->isValid($retrieveParametersFromServer)) {\r\n        $this->_errors[] = 'invalid_logout_request';\r\n        $this->_errorReason = $logoutRequest->getError();\r\n    } else {\r\n        // ...\r\n        $this->redirectTo($this->getSLOurl(), $parameters);\r\n    }\r\n}\r\n```\r\n\r\nWhat this means is that the function never returns, and the logout event never fires, which means that the user's session doesn't actually get cleared.\r\n\r\nI'm not sure what the best option is here - we can look for that `$_GET` variable and fire off the logout event early (although that has problems because we're not verifying the logout request before firing the event)."},"comment":{"url":"https://api.github.com/repos/aacotroneo/laravel-saml2/issues/comments/138585257","html_url":"https://github.com/aacotroneo/laravel-saml2/issues/12#issuecomment-138585257","issue_url":"https://api.github.com/repos/aacotroneo/laravel-saml2/issues/12","id":138585257,"user":{"login":"samlev","id":462466,"avatar_url":"https://avatars.githubusercontent.com/u/462466?v=3","gravatar_id":"","url":"https://api.github.com/users/samlev","html_url":"https://github.com/samlev","followers_url":"https://api.github.com/users/samlev/followers","following_url":"https://api.github.com/users/samlev/following{/other_user}","gists_url":"https://api.github.com/users/samlev/gists{/gist_id}","starred_url":"https://api.github.com/users/samlev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/samlev/subscriptions","organizations_url":"https://api.github.com/users/samlev/orgs","repos_url":"https://api.github.com/users/samlev/repos","events_url":"https://api.github.com/users/samlev/events{/privacy}","received_events_url":"https://api.github.com/users/samlev/received_events","type":"User","site_admin":false},"created_at":"2015-09-08T14:37:16Z","updated_at":"2015-09-08T14:37:16Z","body":"Ok, to explain better...\r\n\r\nYou have two apps, A and B, both logged in with the same IDP.\r\n\r\nApp A calls `logout()`, which contacts the IDP, and starts a logout request. The IDP calls each of the other applications (B) on their slo URL with `SAMLRequest`, and then returns to the requestiong application (A) with `SAMLResponse`\r\n\r\n![Saml slo model](http://i0.wp.com/xacmlinfo.org/wp-content/uploads/2013/06/saml-logout.png)\r\n\r\nThe problem here is this:\r\n\r\nWhen app B gets the `SAMLRequest`, and the OneLogin library redirects back to the IDP *before* the `LogoutEvent` is fired:\r\n\r\n```php\r\nclass Saml2Controller extends Controller\r\n{\r\n    // ...\r\n    public function sls()\r\n    {\r\n        $error = $this->saml2Auth->sls();\r\n        if (!empty($error)) {\r\n            throw new \\Exception(\"Could not log out\");\r\n        }\r\n        Event::fire(new Saml2LogoutEvent());\r\n        return Redirect::to(Config::get('saml2::settings.logoutRoute')); //may be set a configurable default\r\n    }\r\n    // ...\r\n}\r\nclass Saml2Auth\r\n{\r\n    // ...\r\n    function sls()\r\n    {\r\n        $auth = $this->auth;\r\n\r\n        $keep_local_session = true; //we don't touch session here\r\n        $auth->processSLO($keep_local_session);\r\n\r\n        $errors = $auth->getErrors();\r\n\r\n        return $errors;\r\n    }\r\n    // ...\r\n}\r\n\r\nclass OneLogin_Saml2_Auth\r\n{\r\n    //...\r\n    public function processSLO($keepLocalSession = false, $requestId = null, $retrieveParametersFromServer = false)\r\n    {\r\n        $this->_errors = array();\r\n        if (isset($_GET) && isset($_GET['SAMLResponse'])) {\r\n            $logoutResponse = new OneLogin_Saml2_LogoutResponse($this->_settings, $_GET['SAMLResponse']);\r\n            if (!$logoutResponse->isValid($requestId, $retrieveParametersFromServer)) {\r\n                $this->_errors[] = 'invalid_logout_response';\r\n                $this->_errorReason = $logoutResponse->getError();\r\n            } else if ($logoutResponse->getStatus() !== OneLogin_Saml2_Constants::STATUS_SUCCESS) {\r\n                $this->_errors[] = 'logout_not_success';\r\n            } else {\r\n                if (!$keepLocalSession) {\r\n                    OneLogin_Saml2_Utils::deleteLocalSession();\r\n                }\r\n            }\r\n        } else if (isset($_GET) && isset($_GET['SAMLRequest'])) {\r\n            $logoutRequest = new OneLogin_Saml2_LogoutRequest($this->_settings, $_GET['SAMLRequest']);\r\n            if (!$logoutRequest->isValid($retrieveParametersFromServer)) {\r\n                $this->_errors[] = 'invalid_logout_request';\r\n                $this->_errorReason = $logoutRequest->getError();\r\n            } else {\r\n                if (!$keepLocalSession) {\r\n                    OneLogin_Saml2_Utils::deleteLocalSession();\r\n                }\r\n                $inResponseTo = OneLogin_Saml2_LogoutRequest::getID(gzinflate(base64_decode($_GET['SAMLRequest'])));\r\n                $responseBuilder = new OneLogin_Saml2_LogoutResponse($this->_settings);\r\n                $responseBuilder->build($inResponseTo);\r\n                $logoutResponse = $responseBuilder->getResponse();\r\n\r\n                $parameters = array('SAMLResponse' => $logoutResponse);\r\n                if (isset($_GET['RelayState'])) {\r\n                    $parameters['RelayState'] = $_GET['RelayState'];\r\n                }\r\n\r\n                $security = $this->_settings->getSecurityData();\r\n                if (isset($security['logoutResponseSigned']) && $security['logoutResponseSigned']) {\r\n                    $signature = $this->buildResponseSignature($logoutResponse, $parameters['RelayState']);\r\n                    $parameters['SigAlg'] = XMLSecurityKey::RSA_SHA1;\r\n                    $parameters['Signature'] = $signature;\r\n                }\r\n\r\n                $this->redirectTo($this->getSLOurl(), $parameters);\r\n            }\r\n        } else {\r\n            $this->_errors[] = 'invalid_binding';\r\n            throw new OneLogin_Saml2_Error(\r\n                'SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding',\r\n                OneLogin_Saml2_Error::SAML_LOGOUTMESSAGE_NOT_FOUND\r\n            );\r\n        }\r\n    }\r\n    // ...\r\n}\r\n```\r\n\r\nThe call path goes like this:\r\n\r\n* **A** - initiate logout _request_\r\n* **IDP** - receive logout _request_\r\n* **IDB** - fire logout _request_ at **B**\r\n* **B** - receive logout _request_:\r\n\r\n```php\r\nSaml2Controller->sls() {\r\n    $error = $this->saml2Auth->sls() {\r\n        $auth->processSLO($keep_local_session) {\r\n            if (isset($_GET) && isset($_GET['SAMLRequest'])) {\r\n                // check for errors in the request\r\n                $this->redirectTo($this->getSLOurl(), $parameters);\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n* **IDP** - fire logout _response_ at **A**\r\n* **A** - receive logout _request_:\r\n\r\n```php\r\nSaml2Controller->sls() {\r\n    $error = $this->saml2Auth->sls() {\r\n        $auth->processSLO($keep_local_session) {\r\n            if (isset($_GET) && isset($_GET['SAMLResponse'])) {\r\n                // check for errors in the request\r\n            }\r\n        }\r\n        // check for errors\r\n        return $errors;\r\n    }\r\n    // check for errors\r\n    Event::fire(new Saml2LogoutEvent());\r\n    return Redirect::to(Config::get('saml2::settings.logoutRoute'));\r\n}\r\n```\r\n\r\nSo application B never receives the `Saml2LogoutEvent`, and the user's session is never cleared on application B."}}