Commit be14b93c authored by Stan Hu's avatar Stan Hu

Merge branch '4459-redirect-users-back-to-secondary-after-logout-login' into 'master'

Geo - Redirect user back to the secondary after a logout & re-login via the primary

Closes #4459

See merge request gitlab-org/gitlab-ee!8157
parents 84a74977 8c600f41
...@@ -26,7 +26,11 @@ module EE ...@@ -26,7 +26,11 @@ module EE
def gitlab_geo_logout def gitlab_geo_logout
return unless ::Gitlab::Geo.secondary? return unless ::Gitlab::Geo.secondary?
oauth = ::Gitlab::Geo::OauthSession.new(access_token: session[:access_token]) oauth = ::Gitlab::Geo::OauthSession.new(
access_token: session[:access_token],
return_to: safe_redirect_path_for_url(request.referer)
)
@geo_logout_state = oauth.generate_logout_state # rubocop:disable Gitlab/ModuleWithInstanceVariables @geo_logout_state = oauth.generate_logout_state # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
......
...@@ -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
...@@ -12,9 +11,7 @@ class Oauth::GeoAuthController < ActionController::Base ...@@ -12,9 +11,7 @@ class Oauth::GeoAuthController < ActionController::Base
redirect_to oauth.authorize_url(redirect_uri: oauth_geo_callback_url, state: params[:state]) redirect_to oauth.authorize_url(redirect_uri: oauth_geo_callback_url, state: params[:state])
end end
# 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
...@@ -22,23 +19,22 @@ class Oauth::GeoAuthController < ActionController::Base ...@@ -22,23 +19,22 @@ class Oauth::GeoAuthController < ActionController::Base
token = oauth.get_token(params[:code], redirect_uri: oauth_geo_callback_url) token = oauth.get_token(params[:code], redirect_uri: oauth_geo_callback_url)
remote_user = oauth.authenticate_with_gitlab(token) remote_user = oauth.authenticate_with_gitlab(token)
user = UserFinder.new(remote_user['id']).find_by_id
user = User.find_by(id: remote_user['id']) if user && bypass_sign_in(user)
if user && sign_in(user, bypass: true)
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
end end
end end
# rubocop: enable CodeReuse/ActiveRecord
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 +42,24 @@ class Oauth::GeoAuthController < ActionController::Base ...@@ -46,11 +42,24 @@ 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
# Prevent alert from popping up on the first page shown after authentication.
flash[:alert] = nil
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
attr_reader :status include Gitlab::Utils::StrongMemoize
attr_reader :state
def initialize(user, params = {}) def initialize(user, params = {})
@params = params
@current_user = user @current_user = user
@state = params[:state]
end end
def execute def execute
return error('access token not found') unless access_token return error('Access token could not be found') unless access_token.present?
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)
if current_user == user if user && user == current_user
success success(return_to: user_return_to)
end
else else
error(status) error('User could not be found')
end 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)
else
nil
end
end end
end end
def oauth_session
@oauth_session ||= Gitlab::Geo::OauthSession.new(state: state)
end
def user_return_to
full_path = oauth_session.get_oauth_state_return_to_full_path
Gitlab::Utils.append_path(geo_node_url, full_path)
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
...@@ -8,46 +8,31 @@ module Gitlab ...@@ -8,46 +8,31 @@ module Gitlab
attr_accessor :return_to attr_accessor :return_to
def oauth_state_valid? def oauth_state_valid?
return false unless state salt, hmac, return_to = state.to_s.split(':', 3)
LoginState.new(salt, return_to).valid?(hmac)
salt, hmac, return_to = state.split(':', 3)
return false unless return_to
hmac == generate_oauth_hmac(salt, return_to)
end end
def generate_oauth_state def generate_oauth_state
return unless return_to self.state = LoginState.new(oauth_salt, return_to).encode
hmac = generate_oauth_hmac(oauth_salt, return_to)
self.state = "#{oauth_salt}:#{hmac}:#{return_to}"
end end
def generate_logout_state def generate_logout_state
return unless access_token self.state = LogoutState.new(oauth_salt, access_token, return_to).encode
cipher = logout_token_cipher(oauth_salt, :encrypt)
encrypted = cipher.update(access_token) + cipher.final
self.state = "#{oauth_salt}:#{Base64.urlsafe_encode64(encrypted)}"
rescue OpenSSL::OpenSSLError
false
end end
def extract_logout_token def extract_logout_token
return unless state salt, encrypted, return_to = state.to_s.split(':', 3)
LogoutState.new(salt, encrypted, return_to).decode
salt, encrypted = state.split(':', 2)
decipher = logout_token_cipher(salt, :decrypt)
decipher.update(Base64.urlsafe_decode64(encrypted)) + decipher.final
rescue OpenSSL::OpenSSLError
false
end end
def get_oauth_state_return_to def get_oauth_state_return_to
state.split(':', 3)[2] if state state.split(':', 3)[2] if state
end end
def get_oauth_state_return_to_full_path
ReturnToLocation.new(get_oauth_state_return_to).full_path
end
def authorize_url(params = {}) def authorize_url(params = {})
oauth_client.auth_code.authorize_url(params) oauth_client.auth_code.authorize_url(params)
end end
...@@ -65,28 +50,122 @@ module Gitlab ...@@ -65,28 +50,122 @@ module Gitlab
private private
def generate_oauth_hmac(salt, return_to) class LoginState
return false unless return_to def initialize(salt, return_to)
@salt = salt
@return_to = return_to
end
def valid?(hmac)
return false unless salt && return_to
hmac == generate_hmac
end
def encode
return unless salt && return_to
"#{salt}:#{generate_hmac}:#{return_to}"
end
private
attr_reader :salt, :return_to
def generate_hmac
digest = OpenSSL::Digest.new('sha256')
key = Gitlab::Application.secrets.secret_key_base + salt
OpenSSL::HMAC.hexdigest(digest, key, return_to)
end
end
class LogoutState
def initialize(salt, token, return_to)
@salt = salt
@token = token
@return_to = return_to
end
def decode
return unless salt && token
digest = OpenSSL::Digest.new('sha256') decrypt = cipher(salt, :decrypt)
key = Gitlab::Application.secrets.secret_key_base + salt decrypt.update(Base64.urlsafe_decode64(token)) + decrypt.final
OpenSSL::HMAC.hexdigest(digest, key, return_to) rescue OpenSSL::OpenSSLError
nil
end
def encode
return unless token
encrypt = cipher(salt, :encrypt)
encrypted = encrypt.update(token) + encrypt.final
encoded = Base64.urlsafe_encode64(encrypted)
"#{salt}:#{encoded}:#{full_path}"
rescue OpenSSL::OpenSSLError
nil
end
private
attr_reader :salt, :token, :return_to
def cipher(salt, operation)
cipher = OpenSSL::Cipher::AES.new(128, :CBC)
cipher.__send__(operation) # rubocop:disable GitlabSecurity/PublicSend
cipher.iv = salt
cipher.key = Settings.attr_encrypted_db_key_base[0..15]
cipher
end
def full_path
ReturnToLocation.new(return_to).full_path
end
end end
def logout_token_cipher(salt, operation) class ReturnToLocation
cipher = OpenSSL::Cipher::AES.new(128, :CBC) def initialize(location)
cipher.__send__(operation) # rubocop:disable GitlabSecurity/PublicSend @location = location
cipher.iv = salt end
cipher.key = Settings.attr_encrypted_db_key_base[0..15]
cipher def full_path
uri = parse_uri(location)
if uri
path = remove_domain_from_uri(uri)
path = add_fragment_back_to_path(uri, path)
path
end
end
private
attr_reader :location
def parse_uri(location)
location && URI.parse(location.sub(%r{\A\/\/+}, '/'))
rescue URI::InvalidURIError
nil
end
def remove_domain_from_uri(uri)
[uri.path.sub(%r{\A\/+}, '/'), uri.query].compact.join('?')
end
def add_fragment_back_to_path(uri, path)
[path, uri.fragment].compact.join('#')
end
end end
def oauth_salt def oauth_salt
@salt ||= SecureRandom.hex(8) @oauth_salt ||= SecureRandom.hex(8)
end end
def oauth_client def oauth_client
@client ||= begin @oauth_client ||= begin
::OAuth2::Client.new( ::OAuth2::Client.new(
oauth_app.uid, oauth_app.uid,
oauth_app.secret, oauth_app.secret,
......
...@@ -2,9 +2,11 @@ require 'spec_helper' ...@@ -2,9 +2,11 @@ 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(:node) { create(:geo_node) }
let(:access_token) { create(:doorkeeper_access_token, resource_owner_id: user.id).token } let(:oauth_app) { node.oauth_application }
let(:auth_state) { Gitlab::Geo::OauthSession.new(access_token: access_token, return_to: projects_url).generate_oauth_state } let(:access_token) { create(:doorkeeper_access_token, resource_owner_id: user.id, application: oauth_app) }
let(:oauth_session) { Gitlab::Geo::OauthSession.new(access_token: access_token.token, return_to: projects_url) }
let(:auth_state) { oauth_session.generate_oauth_state }
let(:primary_node_url) { 'http://localhost:3001/' } let(:primary_node_url) { 'http://localhost:3001/' }
before do before do
...@@ -30,7 +32,8 @@ describe Oauth::GeoAuthController do ...@@ -30,7 +32,8 @@ 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(:oauth_session) { Gitlab::Geo::OauthSession.new(access_token: access_token.token, return_to: projects_url) }
let(:callback_state) { oauth_session.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
...@@ -51,6 +54,12 @@ describe Oauth::GeoAuthController do ...@@ -51,6 +54,12 @@ describe Oauth::GeoAuthController do
expect(response).to redirect_to(projects_url) expect(response).to redirect_to(projects_url)
end end
it 'does not display a flash message if state is valid' do
get :callback, state: callback_state
expect(controller).to set_flash[:alert].to(nil)
end
end end
context 'invalid credentials' do context 'invalid credentials' do
...@@ -58,7 +67,7 @@ describe Oauth::GeoAuthController do ...@@ -58,7 +67,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 +96,27 @@ describe Oauth::GeoAuthController do ...@@ -87,23 +96,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")
......
...@@ -52,31 +52,62 @@ describe Gitlab::Geo::OauthSession do ...@@ -52,31 +52,62 @@ describe Gitlab::Geo::OauthSession do
end end
describe '#get_oauth_state_return_to' do describe '#get_oauth_state_return_to' do
subject { described_class.new(state: valid_state) }
it 'returns return_to value' do it 'returns return_to value' do
subject = described_class.new(state: valid_state)
expect(subject.get_oauth_state_return_to).to eq(oauth_return_to) expect(subject.get_oauth_state_return_to).to eq(oauth_return_to)
end end
end end
describe '#get_oauth_state_return_to_full_path' do
it 'removes the domain from return_to value' do
subject = described_class.new(state: valid_state)
expect(subject.get_oauth_state_return_to_full_path).to eq('/oauth/geo/callback')
end
end
describe '#generate_logout_state' do describe '#generate_logout_state' do
it 'returns nil when access_token is not defined' do it 'returns nil when access_token is not defined' do
expect(described_class.new.generate_logout_state).to be_nil expect(described_class.new.generate_logout_state).to be_nil
end end
it 'returns false when encryptation fails' do it 'returns false when encryptation fails' do
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 }
expect(subject.generate_logout_state).to be_falsey expect(subject.generate_logout_state).to be_falsey
end end
it 'returns a string with salt and encrypted access token colon separated' do it 'returns a string with salt, encrypted access token, and return_to colon separated' do
state = described_class.new(access_token: access_token).generate_logout_state subject = described_class.new(access_token: access_token, return_to: oauth_return_to)
state = subject.generate_logout_state
expect(state).to be_a String expect(state).to be_a String
expect(state).not_to be_blank expect(state).not_to be_blank
salt, encrypted = state.split(':', 2) salt, encrypted, return_to = state.split(':', 3)
expect(salt).not_to be_blank expect(salt).not_to be_blank
expect(encrypted).not_to be_blank expect(encrypted).not_to be_blank
expect(return_to).not_to be_blank
end
it 'include a empty value for return_to into state when return_to param is not defined' do
subject = described_class.new(access_token: access_token)
state = subject.generate_logout_state
_, _, return_to = state.split(':', 3)
expect(return_to).to eq ''
end
it 'does not include the host from return_to param into into the state' do
subject = described_class.new(access_token: access_token, return_to: oauth_return_to)
state = subject.generate_logout_state
_, _, return_to = state.split(':', 3)
expect(return_to).to eq '/oauth/geo/callback'
end end
end end
...@@ -87,9 +118,15 @@ describe Gitlab::Geo::OauthSession do ...@@ -87,9 +118,15 @@ 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 allow_any_instance_of(OpenSSL::Cipher::AES)
allow_any_instance_of(OpenSSL::Cipher::AES).to receive(:final) { raise OpenSSL::OpenSSLError } .to receive(:final) { raise OpenSSL::OpenSSLError }
expect(subject.extract_logout_token).to be_falsey expect(subject.extract_logout_token).to be_falsey
end end
......
...@@ -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_return_to) { '/project/test' }
let(:oauth_session) { Gitlab::Geo::OauthSession.new(access_token: access_token.token, return_to: oauth_return_to) }
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
it 'returns error when incorrect encoding' do it 'returns error when token has incorrect encoding' do
invalid_token = "\xD800\xD801\xD802" allow_any_instance_of(Gitlab::Geo::OauthSession)
allow_any_instance_of(Gitlab::Geo::OauthSession).to receive(:extract_logout_token) { invalid_token } .to receive(:extract_logout_token)
.and_return("\xD800\xD801\xD802")
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 error when current user is nil' do
result = described_class.new(user, { state: logout_state }).execute result = described_class.new(nil, state: logout_state).execute
expect(result[:status]).to eq(:success)
expect(result).to eq(status: :error, message: 'User could not be found')
end
it 'returns error when token owner could not be found' do
allow(User).to receive(:find).with(user.id).and_return(nil)
result = described_class.new(user, state: logout_state).execute
expect(result).to eq(status: :error, message: 'User could not be found')
end
it 'returns error when token does not belong to the current user' do
result = described_class.new(create(:user), state: logout_state).execute
expect(result).to eq(status: :error, message: 'User could not be found')
end
context 'when token is valid' do
it 'returns success' do
result = described_class.new(user, state: logout_state).execute
expect(result).to include(status: :success)
end
context 'when OAuth session return_to param is nil' do
it 'returns the Geo node URL associated with OAuth application to redirect the user back' do
oauth_session = Gitlab::Geo::OauthSession.new(access_token: access_token.token, return_to: nil)
logout_state = oauth_session.generate_logout_state
result = described_class.new(user, state: logout_state).execute
expect(result).to include(return_to: node.url)
end
end
context 'when OAuth session return_to param is empty' do
it 'returns the Geo node URL associated with OAuth application to redirect the user back' do
oauth_session = Gitlab::Geo::OauthSession.new(access_token: access_token.token, return_to: '')
logout_state = oauth_session.generate_logout_state
result = described_class.new(user, state: logout_state).execute
expect(result).to include(return_to: node.url)
end
end
context 'when OAuth session return_to param is set' do
it 'returns the fullpath to the Geo node to redirect the user back' do
result = described_class.new(user, state: logout_state).execute
expect(result).to include(return_to: "#{node.url.chomp('/')}/project/test")
end
it 'replaces the host with the Geo node associated with OAuth application' do
oauth_return_to = 'http://fake-secondary/project/test'
oauth_session = Gitlab::Geo::OauthSession.new(access_token: access_token.token, return_to: oauth_return_to)
logout_state = oauth_session.generate_logout_state
result = described_class.new(user, state: logout_state).execute
expect(result).to include(return_to: "#{node.url.chomp('/')}/project/test")
end
it 'handles leading and trailing slashes correctly on return_to path' do
oauth_return_to = '//project/test'
oauth_session = Gitlab::Geo::OauthSession.new(access_token: access_token.token, return_to: oauth_return_to)
logout_state = oauth_session.generate_logout_state
result = described_class.new(user, state: logout_state).execute
expect(result).to include(return_to: "#{node.url.chomp('/')}/project/test")
end
end
end end
end end
end end
...@@ -16,6 +16,11 @@ module Gitlab ...@@ -16,6 +16,11 @@ module Gitlab
str.force_encoding(Encoding::UTF_8) str.force_encoding(Encoding::UTF_8)
end end
# Append path to host, making sure there's one single / in between
def append_path(host, path)
"#{host.to_s.sub(%r{\/+$}, '')}/#{path.to_s.sub(%r{^\/+}, '')}"
end
# A slugified version of the string, suitable for inclusion in URLs and # A slugified version of the string, suitable for inclusion in URLs and
# domain names. Rules: # domain names. Rules:
# #
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::Utils do describe Gitlab::Utils do
delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string, delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string,
:bytes_to_megabytes, to: :described_class :bytes_to_megabytes, :append_path, to: :described_class
describe '.slugify' do describe '.slugify' do
{ {
...@@ -106,4 +106,25 @@ describe Gitlab::Utils do ...@@ -106,4 +106,25 @@ describe Gitlab::Utils do
expect(bytes_to_megabytes(bytes)).to eq(1) expect(bytes_to_megabytes(bytes)).to eq(1)
end end
end end
describe '.append_path' do
using RSpec::Parameterized::TableSyntax
where(:host, :path, :result) do
'http://test/' | '/foo/bar' | 'http://test/foo/bar'
'http://test/' | '//foo/bar' | 'http://test/foo/bar'
'http://test//' | '/foo/bar' | 'http://test/foo/bar'
'http://test' | 'foo/bar' | 'http://test/foo/bar'
'http://test//' | '' | 'http://test/'
'http://test//' | nil | 'http://test/'
'' | '/foo/bar' | '/foo/bar'
nil | '/foo/bar' | '/foo/bar'
end
with_them do
it 'makes sure there is only one slash as path separator' do
expect(append_path(host, path)).to eq(result)
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