Redirect user back to secondary after logout/login

Redirect user back to the secondary after a logout & re-login
via the primary.
parent 8c27b53d
...@@ -3,7 +3,6 @@ class Oauth::GeoAuthController < ActionController::Base ...@@ -3,7 +3,6 @@ class Oauth::GeoAuthController < ActionController::Base
rescue_from OAuth2::Error, with: :auth rescue_from OAuth2::Error, with: :auth
def auth def auth
oauth = Gitlab::Geo::OauthSession.new(state: params[:state])
unless oauth.oauth_state_valid? unless oauth.oauth_state_valid?
redirect_to root_url redirect_to root_url
return return
...@@ -14,7 +13,6 @@ class Oauth::GeoAuthController < ActionController::Base ...@@ -14,7 +13,6 @@ class Oauth::GeoAuthController < ActionController::Base
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def callback def callback
oauth = Gitlab::Geo::OauthSession.new(state: params[:state])
unless oauth.oauth_state_valid? unless oauth.oauth_state_valid?
redirect_to new_user_session_path redirect_to new_user_session_path
return return
...@@ -25,7 +23,7 @@ class Oauth::GeoAuthController < ActionController::Base ...@@ -25,7 +23,7 @@ class Oauth::GeoAuthController < ActionController::Base
user = User.find_by(id: remote_user['id']) user = User.find_by(id: remote_user['id'])
if user && sign_in(user, bypass: true) if user && bypass_sign_in(user)
after_sign_in_with_gitlab(token, oauth.get_oauth_state_return_to) after_sign_in_with_gitlab(token, oauth.get_oauth_state_return_to)
else else
invalid_credentials invalid_credentials
...@@ -36,9 +34,10 @@ class Oauth::GeoAuthController < ActionController::Base ...@@ -36,9 +34,10 @@ class Oauth::GeoAuthController < ActionController::Base
def logout def logout
logout = Oauth2::LogoutTokenValidationService.new(current_user, params) logout = Oauth2::LogoutTokenValidationService.new(current_user, params)
result = logout.execute result = logout.execute
if result[:status] == :success if result[:status] == :success
sign_out current_user sign_out current_user
redirect_to root_path after_sign_out_with_gitlab(result[:return_to])
else else
access_token_error(result[:message]) access_token_error(result[:message])
end end
...@@ -46,11 +45,20 @@ class Oauth::GeoAuthController < ActionController::Base ...@@ -46,11 +45,20 @@ class Oauth::GeoAuthController < ActionController::Base
private private
def oauth
@oauth ||= Gitlab::Geo::OauthSession.new(state: params[:state])
end
def after_sign_in_with_gitlab(token, return_to) def after_sign_in_with_gitlab(token, return_to)
session[:access_token] = token session[:access_token] = token
redirect_to(return_to || root_path) redirect_to(return_to || root_path)
end end
def after_sign_out_with_gitlab(return_to)
session[:user_return_to] = return_to
redirect_to(root_path)
end
def invalid_credentials def invalid_credentials
@error = 'Cannot find user to login. Your account may have been deleted.' @error = 'Cannot find user to login. Your account may have been deleted.'
render :error, layout: 'errors' render :error, layout: 'errors'
......
...@@ -89,6 +89,11 @@ class GeoNode < ActiveRecord::Base ...@@ -89,6 +89,11 @@ class GeoNode < ActiveRecord::Base
left_join_status.minimum(:cursor_last_event_id) left_join_status.minimum(:cursor_last_event_id)
end end
# Tries to find a GeoNode by oauth_application_id, returning nil if none could be found.
def find_by_oauth_application_id(oauth_application_id)
where(oauth_application_id: oauth_application_id).take
end
private private
def left_join_status def left_join_status
......
module Oauth2 module Oauth2
class LogoutTokenValidationService < ::BaseService class LogoutTokenValidationService < ::BaseService
include Gitlab::Utils::StrongMemoize
attr_reader :status attr_reader :status
def initialize(user, params = {}) def initialize(user, params = {})
...@@ -8,32 +10,33 @@ module Oauth2 ...@@ -8,32 +10,33 @@ module Oauth2
end end
def execute def execute
return error('access token not found') unless access_token return error('Access token not found') unless access_token
status = AccessTokenValidationService.new(access_token).validate status = AccessTokenValidationService.new(access_token).validate
return error(status) unless status == AccessTokenValidationService::VALID
if status == AccessTokenValidationService::VALID user = User.find(access_token.resource_owner_id)
user = User.find(access_token.resource_owner_id) success(return_to: geo_node_url) if user == current_user
if current_user == user
success
end
else
error(status)
end
end end
def access_token private
@access_token ||= begin
return unless params[:state] && !params[:state].empty?
oauth_session = Gitlab::Geo::OauthSession.new(state: params[:state])
def access_token
strong_memoize(:access_token) do
logout_token = oauth_session.extract_logout_token logout_token = oauth_session.extract_logout_token
return unless logout_token && logout_token.is_utf8?
Doorkeeper::AccessToken.by_token(logout_token) if logout_token&.is_utf8?
Doorkeeper::AccessToken.by_token(logout_token)
end
end end
end end
def oauth_session
@oauth_session ||= Gitlab::Geo::OauthSession.new(state: params[:state])
end
def geo_node_url
GeoNode.find_by_oauth_application_id(access_token.application_id)&.url
end
end end
end end
---
title: Geo - Redirect user back to the secondary after a logout & re-login via the primary
merge_request: 8157
author:
type: fixed
...@@ -35,7 +35,7 @@ module Gitlab ...@@ -35,7 +35,7 @@ module Gitlab
end end
def extract_logout_token def extract_logout_token
return unless state return unless state.present?
salt, encrypted = state.split(':', 2) salt, encrypted = state.split(':', 2)
decipher = logout_token_cipher(salt, :decrypt) decipher = logout_token_cipher(salt, :decrypt)
......
...@@ -3,8 +3,8 @@ require 'spec_helper' ...@@ -3,8 +3,8 @@ require 'spec_helper'
describe Oauth::GeoAuthController do describe Oauth::GeoAuthController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:oauth_app) { create(:doorkeeper_application) } let(:oauth_app) { create(:doorkeeper_application) }
let(:access_token) { create(:doorkeeper_access_token, resource_owner_id: user.id).token } let(:access_token) { create(:doorkeeper_access_token, resource_owner_id: user.id, application: oauth_app) }
let(:auth_state) { Gitlab::Geo::OauthSession.new(access_token: access_token, return_to: projects_url).generate_oauth_state } let(:auth_state) { Gitlab::Geo::OauthSession.new(access_token: access_token.token, return_to: projects_url).generate_oauth_state }
let(:primary_node_url) { 'http://localhost:3001/' } let(:primary_node_url) { 'http://localhost:3001/' }
before do before do
...@@ -30,7 +30,7 @@ describe Oauth::GeoAuthController do ...@@ -30,7 +30,7 @@ describe Oauth::GeoAuthController do
end end
describe 'GET callback' do describe 'GET callback' do
let(:callback_state) { Gitlab::Geo::OauthSession.new(access_token: access_token, return_to: projects_url).generate_oauth_state } let(:callback_state) { Gitlab::Geo::OauthSession.new(access_token: access_token.token, return_to: projects_url).generate_oauth_state }
let(:primary_node_oauth_endpoint) { Gitlab::Geo::OauthSession.new.authorize_url(redirect_uri: oauth_geo_callback_url, state: callback_state) } let(:primary_node_oauth_endpoint) { Gitlab::Geo::OauthSession.new.authorize_url(redirect_uri: oauth_geo_callback_url, state: callback_state) }
context 'redirection' do context 'redirection' do
...@@ -58,7 +58,7 @@ describe Oauth::GeoAuthController do ...@@ -58,7 +58,7 @@ describe Oauth::GeoAuthController do
let(:oauth_error) { OAuth2::Error.new(OAuth2::Response.new(fake_response)) } let(:oauth_error) { OAuth2::Error.new(OAuth2::Response.new(fake_response)) }
before do before do
expect_any_instance_of(Gitlab::Geo::OauthSession).to receive(:get_token) { access_token } expect_any_instance_of(Gitlab::Geo::OauthSession).to receive(:get_token) { access_token.token }
expect_any_instance_of(Gitlab::Geo::OauthSession).to receive(:authenticate_with_gitlab).and_raise(oauth_error) expect_any_instance_of(Gitlab::Geo::OauthSession).to receive(:authenticate_with_gitlab).and_raise(oauth_error)
end end
...@@ -87,23 +87,27 @@ describe Oauth::GeoAuthController do ...@@ -87,23 +87,27 @@ describe Oauth::GeoAuthController do
end end
describe 'GET logout' do describe 'GET logout' do
let(:logout_state) { Gitlab::Geo::OauthSession.new(access_token: access_token).generate_logout_state } let(:oauth_session) { Gitlab::Geo::OauthSession.new(access_token: access_token.token) }
let(:logout_state) { oauth_session.generate_logout_state }
context 'access_token error' do render_views
render_views
before do before do
sign_in(user) sign_in(user)
end end
it 'logs out when correct access_token is informed' do context 'when access_token is valid' do
it 'logs out and redirects to the root_url' do
get :logout, state: logout_state get :logout, state: logout_state
expect(response).to redirect_to root_url expect(response).to redirect_to root_url
end end
end
context 'when access_token is invalid' do
it 'handles access token problems' do it 'handles access token problems' do
allow_any_instance_of(Oauth2::LogoutTokenValidationService).to receive(:execute) { { status: :error, message: :expired } } allow_any_instance_of(Oauth2::LogoutTokenValidationService).to receive(:execute) { { status: :error, message: :expired } }
get :logout, state: logout_state get :logout, state: logout_state
expect(response.body).to include("There is a problem with the OAuth access_token: expired") expect(response.body).to include("There is a problem with the OAuth access_token: expired")
......
...@@ -87,6 +87,12 @@ describe Gitlab::Geo::OauthSession do ...@@ -87,6 +87,12 @@ describe Gitlab::Geo::OauthSession do
expect(subject.extract_logout_token).to be_nil expect(subject.extract_logout_token).to be_nil
end end
it 'returns nil when state is empty' do
subject.state = ''
expect(subject.extract_logout_token).to be_nil
end
it 'returns false when decryptation fails' do it 'returns false when decryptation fails' do
subject.generate_logout_state subject.generate_logout_state
allow_any_instance_of(OpenSSL::Cipher::AES).to receive(:final) { raise OpenSSL::OpenSSLError } allow_any_instance_of(OpenSSL::Cipher::AES).to receive(:final) { raise OpenSSL::OpenSSLError }
......
...@@ -158,6 +158,24 @@ describe GeoNode, type: :model do ...@@ -158,6 +158,24 @@ describe GeoNode, type: :model do
end end
end end
describe '.find_by_oauth_application_id' do
context 'when the Geo node exists' do
it 'returns the Geo node' do
found = described_class.find_by_oauth_application_id(node.oauth_application_id)
expect(found).to eq(node)
end
end
context 'when the Geo node does not exist' do
it 'returns nil' do
found = described_class.find_by_oauth_application_id(-1)
expect(found).to be_nil
end
end
end
describe '#repair' do describe '#repair' do
it 'creates an oauth application for a Geo secondary node' do it 'creates an oauth application for a Geo secondary node' do
stub_current_geo_node(node) stub_current_geo_node(node)
......
require 'spec_helper' require 'spec_helper'
describe Oauth2::LogoutTokenValidationService do describe Oauth2::LogoutTokenValidationService do
let(:user) { FactoryBot.create(:user) } let(:user) { create(:user) }
let(:access_token) { FactoryBot.create(:doorkeeper_access_token, resource_owner_id: user.id).token } let(:node) { create(:geo_node) }
let(:logout_state) { Gitlab::Geo::OauthSession.new(access_token: access_token).generate_logout_state } let(:access_token) { create(:doorkeeper_access_token, resource_owner_id: user.id, application_id: node.oauth_application_id) }
let(:oauth_session) { Gitlab::Geo::OauthSession.new(access_token: access_token.token) }
let(:logout_state) { oauth_session.generate_logout_state }
context '#execute' do context '#execute' do
it 'return error when params are empty' do it 'return error when params are empty' do
result = described_class.new(user, {}).execute result = described_class.new(user, {}).execute
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
end end
it 'returns error when state param is empty' do it 'returns error when state param is nil' do
result = described_class.new(user, { state: nil }).execute result = described_class.new(user, { state: nil }).execute
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
end
it 'returns error when state param is empty' do
result = described_class.new(user, { state: '' }).execute result = described_class.new(user, { state: '' }).execute
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
end end
...@@ -24,12 +31,14 @@ describe Oauth2::LogoutTokenValidationService do ...@@ -24,12 +31,14 @@ describe Oauth2::LogoutTokenValidationService do
allow_any_instance_of(Gitlab::Geo::OauthSession).to receive(:extract_logout_token) { invalid_token } allow_any_instance_of(Gitlab::Geo::OauthSession).to receive(:extract_logout_token) { invalid_token }
result = described_class.new(user, { state: logout_state }).execute result = described_class.new(user, { state: logout_state }).execute
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
end end
it 'returns true when token is valid' do it 'returns success when token is valid' do
result = described_class.new(user, { state: logout_state }).execute result = described_class.new(user, { state: logout_state }).execute
expect(result[:status]).to eq(:success)
expect(result).to eq(status: :success, return_to: node.url)
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment