Commit 4ede4460 authored by Stan Hu's avatar Stan Hu

Fix SSO SAML redirection not including query string

When a user without a SSO session attempted to access anything in a SAML
group with a query string, previously GitLab would redirect the user
back to original path but drop the query string.

We fixed the redirection originally in
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66791, but
`request.path_info` drops query strings. To ensure the query strings are
passed to the `RelayState`, we need to use `request.fullpath`.

Relates to https://gitlab.com/gitlab-org/gitlab/-/issues/338833

Changelog: fixed
parent 1dae49eb
......@@ -307,7 +307,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def user
@user ||= find_routable!(User, params[:id], request.path_info)
@user ||= find_routable!(User, params[:id], request.fullpath)
end
def build_canonical_path(user)
......
......@@ -3,7 +3,7 @@
module ProjectUnauthorized
module ControllerActions
def self.on_routable_not_found
lambda do |routable, path_info|
lambda do |routable, full_path|
return unless routable.is_a?(Project)
label = routable.external_authorization_classification_label
......
......@@ -3,13 +3,13 @@
module RoutableActions
extend ActiveSupport::Concern
def find_routable!(routable_klass, routable_full_path, path_info, extra_authorization_proc: nil)
def find_routable!(routable_klass, routable_full_path, full_path, extra_authorization_proc: nil)
routable = routable_klass.find_by_full_path(routable_full_path, follow_redirects: request.get?)
if routable_authorized?(routable, extra_authorization_proc)
ensure_canonical_path(routable, routable_full_path)
routable
else
perform_not_found_actions(routable, not_found_actions, path_info)
perform_not_found_actions(routable, not_found_actions, full_path)
route_not_found unless performed?
......@@ -21,11 +21,11 @@ module RoutableActions
[ProjectUnauthorized::ControllerActions.on_routable_not_found]
end
def perform_not_found_actions(routable, actions, path_info)
def perform_not_found_actions(routable, actions, full_path)
actions.each do |action|
break if performed?
instance_exec(routable, path_info, &action)
instance_exec(routable, full_path, &action)
end
end
......
......@@ -24,7 +24,7 @@ class Groups::ApplicationController < ApplicationController
end
def group
@group ||= find_routable!(Group, params[:group_id] || params[:id], request.path_info)
@group ||= find_routable!(Group, params[:group_id] || params[:id], request.fullpath)
end
def group_projects
......
......@@ -13,6 +13,6 @@ class Groups::Clusters::IntegrationsController < Clusters::IntegrationsControlle
end
def group
@group ||= find_routable!(Group, params[:group_id] || params[:id], request.path_info)
@group ||= find_routable!(Group, params[:group_id] || params[:id], request.fullpath)
end
end
......@@ -15,7 +15,7 @@ class Groups::ClustersController < Clusters::ClustersController
end
def group
@group ||= find_routable!(Group, params[:group_id] || params[:id], request.path_info)
@group ||= find_routable!(Group, params[:group_id] || params[:id], request.fullpath)
end
def metrics_dashboard_params
......
......@@ -6,7 +6,7 @@ class Profiles::GroupsController < Profiles::ApplicationController
feature_category :users
def update
group = find_routable!(Group, params[:id], request.path_info)
group = find_routable!(Group, params[:id], request.fullpath)
notification_setting = current_user.notification_settings_for(group)
if notification_setting.update(update_params)
......
......@@ -26,7 +26,7 @@ class Projects::ApplicationController < ApplicationController
path = File.join(params[:namespace_id], params[:project_id] || params[:id])
auth_proc = ->(project) { !project.pending_delete? }
@project = find_routable!(Project, path, request.path_info, extra_authorization_proc: auth_proc)
@project = find_routable!(Project, path, request.fullpath, extra_authorization_proc: auth_proc)
end
def build_canonical_path(project)
......
......@@ -10,6 +10,6 @@ class Projects::Clusters::IntegrationsController < ::Clusters::IntegrationsContr
end
def project
@project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), request.path_info)
@project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), request.fullpath)
end
end
......@@ -17,7 +17,7 @@ class Projects::ClustersController < Clusters::ClustersController
end
def project
@project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), request.path_info)
@project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), request.fullpath)
end
def repository
......
......@@ -172,7 +172,7 @@ class UsersController < ApplicationController
private
def user
@user ||= find_routable!(User, params[:username], request.path_info)
@user ||= find_routable!(User, params[:username], request.fullpath)
end
def personal_projects
......
......@@ -6,11 +6,11 @@ module EE
include ::Gitlab::Routing
include ::Gitlab::Utils::StrongMemoize
attr_reader :routable, :path_info
attr_reader :routable, :full_path
def initialize(routable, path_info)
def initialize(routable, full_path)
@routable = routable
@path_info = path_info
@full_path = full_path
end
def should_redirect_to_group_saml_sso?(current_user, request)
......@@ -26,8 +26,8 @@ module EE
module ControllerActions
def self.on_routable_not_found
lambda do |routable, path_info|
redirector = SsoEnforcementRedirect.new(routable, path_info)
lambda do |routable, full_path|
redirector = SsoEnforcementRedirect.new(routable, full_path)
if redirector.should_redirect_to_group_saml_sso?(current_user, request)
redirect_to redirector.sso_redirect_url
......@@ -64,7 +64,7 @@ module EE
def url_params
{
token: root_group.saml_discovery_token,
redirect: path_info
redirect: full_path
}
end
end
......
......@@ -23,13 +23,13 @@ class Groups::Analytics::ApplicationController < ApplicationController
def load_group
return unless params['group_id']
@group = find_routable!(Group, params['group_id'], request.path_info)
@group = find_routable!(Group, params['group_id'], request.fullpath)
end
def load_project
return unless @group && params['project_id']
@project = find_routable!(@group.projects, params['project_id'], request.path_info)
@project = find_routable!(@group.projects, params['project_id'], request.fullpath)
end
private_class_method :increment_usage_counter
......
......@@ -31,7 +31,7 @@ module Subscriptions
private
def find_group
@group ||= find_routable!(Group, params[:id], request.path_info)
@group ||= find_routable!(Group, params[:id], request.fullpath)
end
def group_params
......
......@@ -122,7 +122,7 @@ RSpec.describe 'SAML access enforcement' do
context 'with a merge request' do
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:resource_path) { project_merge_request_path(project, merge_request) }
let(:resource_path) { project_merge_request_path(project, merge_request, { test: "value" }) }
it 'redirects to the SSO page and then merge request page after login' do
visit resource_path
......@@ -131,7 +131,8 @@ RSpec.describe 'SAML access enforcement' do
click_link 'Sign in with Single Sign-On'
expect(current_path).to eq(resource_path)
# Capybara's have_current_path matcher checks the path and query string
expect(page).to have_current_path(resource_path)
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