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 getURL from "discourse-common/lib/get-url";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { findAll } from "discourse/models/login-method";
import { helperContext } from "discourse-common/lib/helpers"; import { helperContext } from "discourse-common/lib/helpers";
export default function logout() { export default function logout({ redirect } = {}) {
const ctx = helperContext(); const ctx = helperContext();
let siteSettings = ctx.siteSettings;
let keyValueStore = ctx.keyValueStore; let keyValueStore = ctx.keyValueStore;
keyValueStore.abandonLocal(); keyValueStore.abandonLocal();
const redirect = siteSettings.logout_redirect;
if (!isEmpty(redirect)) { if (!isEmpty(redirect)) {
window.location.href = redirect; window.location.href = redirect;
return; 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("/"); window.location.href = getURL("/");
} }

View File

@ -278,7 +278,9 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
_handleLogout() { _handleLogout() {
if (this.currentUser) { 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 end
def destroy 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 reset_session
log_off_user log_off_user
if request.xhr? if request.xhr?
render body: nil render json: {
redirect_url: redirect_url
}
else else
redirect_to (params[:return_url] || path("/")) redirect_to redirect_url
end end
end end

View File

@ -1751,6 +1751,44 @@ RSpec.describe SessionController do
expect(session[:current_user_id]).to be_blank expect(session[:current_user_id]).to be_blank
expect(response.cookies["_t"]).to be_blank expect(response.cookies["_t"]).to be_blank
end 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 end
describe '#one_time_password' do describe '#one_time_password' do