DEV: Move logout redirect logic to server and add plugin hook (#11199)

This will allow authentication plugins to provide single-logout functionality by redirect users to the identity provider after logout.
This commit is contained in:
David Taylor 2020-11-11 15:47:42 +00:00 committed by GitHub
parent be07853cc1
commit 255633578c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 62 additions and 18 deletions

View File

@ -1,30 +1,16 @@
import getURL from "discourse-common/lib/get-url";
import { isEmpty } from "@ember/utils";
import { findAll } from "discourse/models/login-method";
import { helperContext } from "discourse-common/lib/helpers";
export default function logout() {
export default function logout({ redirect } = {}) {
const ctx = helperContext();
let siteSettings = ctx.siteSettings;
let keyValueStore = ctx.keyValueStore;
keyValueStore.abandonLocal();
const redirect = siteSettings.logout_redirect;
if (!isEmpty(redirect)) {
window.location.href = redirect;
return;
}
const sso = siteSettings.enable_sso;
const oneAuthenticator =
!siteSettings.enable_local_logins && findAll().length === 1;
if (siteSettings.login_required && (sso || oneAuthenticator)) {
// In this situation visiting most URLs will start the auth process again
// Go to the `/login` page to avoid an immediate redirect
window.location.href = getURL("/login");
return;
}
window.location.href = getURL("/");
}

View File

@ -278,7 +278,9 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
_handleLogout() {
if (this.currentUser) {
this.currentUser.destroySession().then(() => logout());
this.currentUser
.destroySession()
.then((response) => logout({ redirect: response["redirect_url"] }));
}
},
});

View File

@ -442,12 +442,30 @@ class SessionController < ApplicationController
end
def destroy
redirect_url = params[:return_url].presence || SiteSetting.logout_redirect.presence
sso = SiteSetting.enable_sso
only_one_authenticator = !SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1
if sso || only_one_authenticator
# In this situation visiting most URLs will start the auth process again
# Go to the `/login` page to avoid an immediate redirect
redirect_url ||= path("/login")
end
redirect_url ||= path("/")
event_data = { redirect_url: redirect_url, user: current_user }
DiscourseEvent.trigger(:before_session_destroy, event_data)
redirect_url = event_data[:redirect_url]
reset_session
log_off_user
if request.xhr?
render body: nil
render json: {
redirect_url: redirect_url
}
else
redirect_to (params[:return_url] || path("/"))
redirect_to redirect_url
end
end

View File

@ -1751,6 +1751,44 @@ RSpec.describe SessionController do
expect(session[:current_user_id]).to be_blank
expect(response.cookies["_t"]).to be_blank
end
it 'returns the redirect URL in the body for XHR requests' do
user = sign_in(Fabricate(:user))
delete "/session/#{user.username}.json", xhr: true
expect(response.status).to eq(200)
expect(session[:current_user_id]).to be_blank
expect(response.cookies["_t"]).to be_blank
expect(response.parsed_body["redirect_url"]).to eq("/")
end
it 'redirects to /login for SSO' do
SiteSetting.sso_url = "https://example.com/sso"
SiteSetting.enable_sso = true
user = sign_in(Fabricate(:user))
delete "/session/#{user.username}.json", xhr: true
expect(response.status).to eq(200)
expect(response.parsed_body["redirect_url"]).to eq("/login")
end
it 'allows plugins to manipulate redirect URL' do
callback = -> (data) do
data[:redirect_url] = "/myredirect/#{data[:user].username}"
end
DiscourseEvent.on(:before_session_destroy, &callback)
user = sign_in(Fabricate(:user))
delete "/session/#{user.username}.json", xhr: true
expect(response.status).to eq(200)
expect(response.parsed_body["redirect_url"]).to eq("/myredirect/#{user.username}")
ensure
DiscourseEvent.off(:before_session_destroy, &callback)
end
end
describe '#one_time_password' do