Commit 00b3e372 authored by Kerri Miller's avatar Kerri Miller

Avoid #authenticate_user! in #route_not_found

This method, #route_not_found, is executed as the final fallback for
unrecognized routes (as the name might imply.) We want to avoid
`#authenticate_user!` when calling `#route_not_found`;
`#authenticate_user!` can, depending on the request format, return a 401
instead of redirecting to a login page. This opens a subtle security
exploit where anonymous users will receive a 401 response when
attempting to access a private repo, while a recognized user will
receive a 404, exposing the existence of the private, hidden repo.
parent e526ee6e
...@@ -16,7 +16,7 @@ class ApplicationController < ActionController::Base ...@@ -16,7 +16,7 @@ class ApplicationController < ActionController::Base
include Gitlab::Tracking::ControllerConcern include Gitlab::Tracking::ControllerConcern
include Gitlab::Experimentation::ControllerConcern include Gitlab::Experimentation::ControllerConcern
before_action :authenticate_user! before_action :authenticate_user!, except: [:route_not_found]
before_action :enforce_terms!, if: :should_enforce_terms? before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket! before_action :validate_user_service_ticket!
before_action :check_password_expiration before_action :check_password_expiration
...@@ -95,7 +95,9 @@ class ApplicationController < ActionController::Base ...@@ -95,7 +95,9 @@ class ApplicationController < ActionController::Base
if current_user if current_user
not_found not_found
else else
authenticate_user! store_location_for(:user, request.fullpath) unless request.xhr?
redirect_to new_user_session_path, alert: I18n.t('devise.failure.unauthenticated')
end end
end end
......
---
title: Standardize error response when route is missing
merge_request:
author:
type: security
...@@ -56,6 +56,16 @@ describe Groups::BoardsController do ...@@ -56,6 +56,16 @@ describe Groups::BoardsController do
let(:parent) { group } let(:parent) { group }
it_behaves_like 'returns recently visited boards' it_behaves_like 'returns recently visited boards'
context 'unauthenticated' do
it 'returns a 401' do
sign_out(user)
list_boards(recent: true)
expect(response).to have_gitlab_http_status(401)
end
end
end end
describe 'GET show' do describe 'GET show' do
......
...@@ -31,6 +31,16 @@ describe Projects::BoardsController do ...@@ -31,6 +31,16 @@ describe Projects::BoardsController do
let(:parent) { project } let(:parent) { project }
it_behaves_like 'returns recently visited boards' it_behaves_like 'returns recently visited boards'
context 'unauthenticated' do
it 'returns a 302' do
sign_out(user)
list_boards(recent: true)
expect(response).to have_gitlab_http_status(302)
end
end
end end
describe 'GET show' do describe 'GET show' do
......
...@@ -72,10 +72,10 @@ describe Projects::ManagedLicensesController do ...@@ -72,10 +72,10 @@ describe Projects::ManagedLicensesController do
context 'with no logged in user' do context 'with no logged in user' do
let(:user) { unlogged_user } let(:user) { unlogged_user }
it 'returns an unauthorized status' do it 'returns a redirect' do
subject subject
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:redirect)
end end
end end
...@@ -122,10 +122,10 @@ describe Projects::ManagedLicensesController do ...@@ -122,10 +122,10 @@ describe Projects::ManagedLicensesController do
context 'with no logged in user' do context 'with no logged in user' do
let(:user) { unlogged_user } let(:user) { unlogged_user }
it 'returns an unauthorized status' do it 'returns a redirect' do
subject subject
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:redirect)
end end
end end
...@@ -235,10 +235,10 @@ describe Projects::ManagedLicensesController do ...@@ -235,10 +235,10 @@ describe Projects::ManagedLicensesController do
new_software_license_policy_attributes new_software_license_policy_attributes
end end
it 'returns an unauthorized status' do it 'returns a redirect' do
expect { subject }.not_to change { project.software_license_policies.count } expect { subject }.not_to change { project.software_license_policies.count }
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:redirect)
end end
end end
...@@ -347,10 +347,10 @@ describe Projects::ManagedLicensesController do ...@@ -347,10 +347,10 @@ describe Projects::ManagedLicensesController do
new_software_license_policy_attributes new_software_license_policy_attributes
end end
it 'returns an unauthorized status' do it 'returns a redirect' do
expect { subject }.not_to change { project.software_license_policies.count } expect { subject }.not_to change { project.software_license_policies.count }
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:redirect)
end end
end end
...@@ -452,10 +452,10 @@ describe Projects::ManagedLicensesController do ...@@ -452,10 +452,10 @@ describe Projects::ManagedLicensesController do
new_software_license_policy_attributes new_software_license_policy_attributes
end end
it 'returns an unauthorized status' do it 'returns a redirect' do
expect { subject }.not_to change { project.software_license_policies.count } expect { subject }.not_to change { project.software_license_policies.count }
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:redirect)
end end
end end
......
...@@ -478,10 +478,10 @@ describe Projects::Settings::OperationsController do ...@@ -478,10 +478,10 @@ describe Projects::Settings::OperationsController do
sign_out(user) sign_out(user)
end end
it 'returns unauthorized status' do it 'returns a redirect' do
reset_alerting_token reset_alerting_token
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:redirect)
end end
end end
......
...@@ -5,16 +5,6 @@ require 'spec_helper' ...@@ -5,16 +5,6 @@ require 'spec_helper'
shared_examples 'returns recently visited boards' do shared_examples 'returns recently visited boards' do
let(:boards) { create_list(:board, 8, parent: parent) } let(:boards) { create_list(:board, 8, parent: parent) }
context 'unauthenticated' do
it 'returns a 401' do
sign_out(user)
list_boards(recent: true)
expect(response).to have_gitlab_http_status(401)
end
end
it 'returns last 4 visited boards' do it 'returns last 4 visited boards' do
[0, 2, 5, 3, 7, 1].each_with_index do |board_index, i| [0, 2, 5, 3, 7, 1].each_with_index do |board_index, i|
visit_board(boards[board_index], Time.now + i.minutes) visit_board(boards[board_index], Time.now + i.minutes)
......
...@@ -202,7 +202,7 @@ describe ApplicationController do ...@@ -202,7 +202,7 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
it 'redirects to login page via authenticate_user! if not authenticated' do it 'redirects to login page if not authenticated' do
get :index get :index
expect(response).to redirect_to new_user_session_path expect(response).to redirect_to new_user_session_path
......
...@@ -142,7 +142,7 @@ describe Projects::CommitsController do ...@@ -142,7 +142,7 @@ describe Projects::CommitsController do
context 'token authentication' do context 'token authentication' do
context 'public project' do context 'public project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do it_behaves_like 'authenticates sessionless user', :show, :atom, { public: true, ignore_incrementing: true } do
before do before do
public_project = create(:project, :repository, :public) public_project = create(:project, :repository, :public)
...@@ -152,7 +152,7 @@ describe Projects::CommitsController do ...@@ -152,7 +152,7 @@ describe Projects::CommitsController do
end end
context 'private project' do context 'private project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: false do it_behaves_like 'authenticates sessionless user', :show, :atom, { public: false, ignore_incrementing: true } do
before do before do
private_project = create(:project, :repository, :private) private_project = create(:project, :repository, :private)
private_project.add_maintainer(user) private_project.add_maintainer(user)
......
...@@ -146,7 +146,7 @@ describe Projects::ErrorTrackingController do ...@@ -146,7 +146,7 @@ describe Projects::ErrorTrackingController do
it 'redirects to sign-in page' do it 'redirects to sign-in page' do
post :list_projects, params: list_projects_params post :list_projects, params: list_projects_params
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:redirect)
end end
end end
......
...@@ -1440,7 +1440,7 @@ describe Projects::IssuesController do ...@@ -1440,7 +1440,7 @@ describe Projects::IssuesController do
context 'private project with token authentication' do context 'private project with token authentication' do
let(:private_project) { create(:project, :private) } let(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user', :index, :atom do it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
before do before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace) default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
...@@ -1448,7 +1448,7 @@ describe Projects::IssuesController do ...@@ -1448,7 +1448,7 @@ describe Projects::IssuesController do
end end
end end
it_behaves_like 'authenticates sessionless user', :calendar, :ics do it_behaves_like 'authenticates sessionless user', :calendar, :ics, ignore_incrementing: true do
before do before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace) default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
......
...@@ -41,7 +41,7 @@ describe Projects::TagsController do ...@@ -41,7 +41,7 @@ describe Projects::TagsController do
context 'private project with token authentication' do context 'private project with token authentication' do
let(:private_project) { create(:project, :repository, :private) } let(:private_project) { create(:project, :repository, :private) }
it_behaves_like 'authenticates sessionless user', :index, :atom do it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
before do before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace) default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
......
...@@ -1149,7 +1149,7 @@ describe ProjectsController do ...@@ -1149,7 +1149,7 @@ describe ProjectsController do
context 'private project with token authentication' do context 'private project with token authentication' do
let(:private_project) { create(:project, :private) } let(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user', :show, :atom do it_behaves_like 'authenticates sessionless user', :show, :atom, ignore_incrementing: true do
before do before do
default_params.merge!(id: private_project, namespace_id: private_project.namespace) default_params.merge!(id: private_project, namespace_id: private_project.namespace)
......
...@@ -827,7 +827,10 @@ describe 'Pipelines', :js do ...@@ -827,7 +827,10 @@ describe 'Pipelines', :js do
context 'when project is private' do context 'when project is private' do
let(:project) { create(:project, :private, :repository) } let(:project) { create(:project, :private, :repository) }
it { expect(page).to have_content 'You need to sign in' } it 'redirects the user to sign_in and displays the flash alert' do
expect(page).to have_content 'You need to sign in'
expect(page.current_path).to eq("/users/sign_in")
end
end end
end end
......
...@@ -15,7 +15,7 @@ describe 'User views tags', :feature do ...@@ -15,7 +15,7 @@ describe 'User views tags', :feature do
it do it do
visit project_tags_path(project, format: :atom) visit project_tags_path(project, format: :atom)
expect(page).to have_gitlab_http_status(401) expect(page.current_path).to eq("/users/sign_in")
end end
end end
......
...@@ -34,8 +34,15 @@ shared_examples 'authenticates sessionless user' do |path, format, params| ...@@ -34,8 +34,15 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
context 'when the personal access token has no api scope', unless: params[:public] do context 'when the personal access token has no api scope', unless: params[:public] do
it 'does not log the user in' do it 'does not log the user in' do
expect(authentication_metrics) # Several instances of where these specs are shared route the request
.to increment(:user_unauthenticated_counter) # through ApplicationController#route_not_found which does not involve
# the usual auth code from Devise, so does not increment the
# :user_unauthenticated_counter
#
unless params[:ignore_incrementing]
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
end
personal_access_token.update(scopes: [:read_user]) personal_access_token.update(scopes: [:read_user])
...@@ -84,8 +91,15 @@ shared_examples 'authenticates sessionless user' do |path, format, params| ...@@ -84,8 +91,15 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
end end
it "doesn't log the user in otherwise", unless: params[:public] do it "doesn't log the user in otherwise", unless: params[:public] do
expect(authentication_metrics) # Several instances of where these specs are shared route the request
.to increment(:user_unauthenticated_counter) # through ApplicationController#route_not_found which does not involve
# the usual auth code from Devise, so does not increment the
# :user_unauthenticated_counter
#
unless params[:ignore_incrementing]
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
end
get path, params: default_params.merge(private_token: 'token') get path, params: default_params.merge(private_token: 'token')
......
...@@ -39,7 +39,7 @@ shared_examples 'todos actions' do ...@@ -39,7 +39,7 @@ shared_examples 'todos actions' do
post_create post_create
end.to change { user.todos.count }.by(0) end.to change { user.todos.count }.by(0)
expect(response).to have_gitlab_http_status(parent.is_a?(Group) ? 401 : 302) expect(response).to have_gitlab_http_status(302)
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