debian-mirror-gitlab/lib/gitlab/auth/auth_finders.rb

368 lines
12 KiB
Ruby
Raw Normal View History

2018-12-13 13:39:08 +05:30
# frozen_string_literal: true
2018-03-17 18:26:18 +05:30
module Gitlab
module Auth
AuthenticationError = Class.new(StandardError)
MissingTokenError = Class.new(AuthenticationError)
TokenNotFoundError = Class.new(AuthenticationError)
ExpiredError = Class.new(AuthenticationError)
RevokedError = Class.new(AuthenticationError)
2019-02-15 15:39:39 +05:30
ImpersonationDisabled = Class.new(AuthenticationError)
2018-03-17 18:26:18 +05:30
UnauthorizedError = Class.new(AuthenticationError)
class InsufficientScopeError < AuthenticationError
attr_reader :scopes
2022-05-07 20:08:51 +05:30
2018-03-17 18:26:18 +05:30
def initialize(scopes)
@scopes = scopes.map { |s| s.try(:name) || s }
end
end
2020-01-01 13:55:28 +05:30
module AuthFinders
2018-03-17 18:26:18 +05:30
include Gitlab::Utils::StrongMemoize
2020-01-01 13:55:28 +05:30
include ActionController::HttpAuthentication::Basic
2020-10-24 23:57:45 +05:30
include ActionController::HttpAuthentication::Token
2018-03-17 18:26:18 +05:30
2019-12-04 20:38:33 +05:30
PRIVATE_TOKEN_HEADER = 'HTTP_PRIVATE_TOKEN'
2018-03-17 18:26:18 +05:30
PRIVATE_TOKEN_PARAM = :private_token
2021-04-29 21:17:54 +05:30
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'
2020-01-01 13:55:28 +05:30
JOB_TOKEN_PARAM = :job_token
2021-04-29 21:17:54 +05:30
DEPLOY_TOKEN_HEADER = 'HTTP_DEPLOY_TOKEN'
2020-01-01 13:55:28 +05:30
RUNNER_TOKEN_PARAM = :token
2020-03-13 15:44:24 +05:30
RUNNER_JOB_TOKEN_PARAM = :token
2018-03-17 18:26:18 +05:30
# Check the Rails session for valid authentication details
def find_user_from_warden
current_request.env['warden']&.authenticate if verified_request?
end
2019-12-04 20:38:33 +05:30
def find_user_from_static_object_token(request_format)
return unless valid_static_objects_format?(request_format)
token = current_request.params[:token].presence || current_request.headers['X-Gitlab-Static-Object-Token'].presence
return unless token
User.find_by_static_object_token(token) || raise(UnauthorizedError)
end
2018-11-29 20:51:05 +05:30
def find_user_from_feed_token(request_format)
return unless valid_rss_format?(request_format)
2021-02-22 17:27:13 +05:30
return if Gitlab::CurrentSettings.disable_feed_token
2018-03-17 18:26:18 +05:30
2018-11-08 19:23:39 +05:30
# NOTE: feed_token was renamed from rss_token but both needs to be supported because
# users might have already added the feed to their RSS reader before the rename
token = current_request.params[:feed_token].presence || current_request.params[:rss_token].presence
2018-03-17 18:26:18 +05:30
return unless token
2018-11-08 19:23:39 +05:30
User.find_by_feed_token(token) || raise(UnauthorizedError)
2018-03-17 18:26:18 +05:30
end
2020-07-28 23:09:34 +05:30
def find_user_from_bearer_token
find_user_from_job_bearer_token ||
find_user_from_access_token
end
2020-01-01 13:55:28 +05:30
def find_user_from_job_token
return unless route_authentication_setting[:job_token_allowed]
2020-06-23 00:09:42 +05:30
return find_user_from_basic_auth_job if route_authentication_setting[:job_token_allowed] == :basic_auth
2020-01-01 13:55:28 +05:30
2020-03-13 15:44:24 +05:30
token = current_request.params[JOB_TOKEN_PARAM].presence ||
current_request.params[RUNNER_JOB_TOKEN_PARAM].presence ||
current_request.env[JOB_TOKEN_HEADER].presence
return unless token
2020-01-01 13:55:28 +05:30
2020-09-03 11:15:55 +05:30
job = find_valid_running_job_by_token!(token)
2020-01-01 13:55:28 +05:30
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
job.user
end
def find_user_from_basic_auth_job
return unless has_basic_credentials?(current_request)
login, password = user_name_and_password(current_request)
return unless login.present? && password.present?
2020-10-24 23:57:45 +05:30
return unless ::Gitlab::Auth::CI_JOB_USER == login
2020-01-01 13:55:28 +05:30
2020-09-03 11:15:55 +05:30
job = find_valid_running_job_by_token!(password)
2021-01-29 00:20:46 +05:30
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
2020-01-01 13:55:28 +05:30
job.user
end
2021-09-30 23:02:18 +05:30
def find_user_from_basic_auth_password
return unless has_basic_credentials?(current_request)
login, password = user_name_and_password(current_request)
return if ::Gitlab::Auth::CI_JOB_USER == login
Gitlab::Auth.find_with_user_password(login, password)
end
def find_user_from_lfs_token
return unless has_basic_credentials?(current_request)
login, token = user_name_and_password(current_request)
2022-08-27 11:52:29 +05:30
user = User.find_by_login(login)
2021-09-30 23:02:18 +05:30
user if user && Gitlab::LfsToken.new(user).token_valid?(token)
end
def find_user_from_personal_access_token
return unless access_token
validate_access_token!
access_token&.user || raise(UnauthorizedError)
end
# We allow Private Access Tokens with `api` scope to be used by web
2018-11-29 20:51:05 +05:30
# requests on RSS feeds or ICS files for backwards compatibility.
# It is also used by GraphQL/API requests.
2021-09-30 23:02:18 +05:30
# And to allow accessing /archive programatically as it was a big pain point
# for users https://gitlab.com/gitlab-org/gitlab/-/issues/28978.
2021-03-08 18:12:59 +05:30
def find_user_from_web_access_token(request_format, scopes: [:api])
2018-11-29 20:51:05 +05:30
return unless access_token && valid_web_access_format?(request_format)
2021-03-08 18:12:59 +05:30
validate_access_token!(scopes: scopes)
2018-11-29 20:51:05 +05:30
2020-07-28 23:09:34 +05:30
::PersonalAccessTokens::LastUsedService.new(access_token).execute
2018-11-29 20:51:05 +05:30
access_token.user || raise(UnauthorizedError)
end
2018-03-17 18:26:18 +05:30
def find_user_from_access_token
return unless access_token
validate_access_token!
2020-07-28 23:09:34 +05:30
::PersonalAccessTokens::LastUsedService.new(access_token).execute
2018-03-17 18:26:18 +05:30
access_token.user || raise(UnauthorizedError)
end
2020-05-24 23:13:21 +05:30
# This returns a deploy token, not a user since a deploy token does not
# belong to a user.
#
# deploy tokens are accepted with deploy token headers and basic auth headers
def deploy_token_from_request
return unless route_authentication_setting[:deploy_token_allowed]
token = current_request.env[DEPLOY_TOKEN_HEADER].presence || parsed_oauth_token
if has_basic_credentials?(current_request)
_, token = user_name_and_password(current_request)
end
deploy_token = DeployToken.active.find_by_token(token)
@current_authenticated_deploy_token = deploy_token # rubocop:disable Gitlab/ModuleWithInstanceVariables
deploy_token
end
2020-10-24 23:57:45 +05:30
def cluster_agent_token_from_authorization_token
return unless route_authentication_setting[:cluster_agent_token_allowed]
return unless current_request.authorization.present?
authorization_token, _options = token_and_options(current_request)
2022-03-02 08:16:31 +05:30
::Clusters::AgentToken.active.find_by_token(authorization_token)
2020-10-24 23:57:45 +05:30
end
2020-01-01 13:55:28 +05:30
def find_runner_from_token
return unless api_request?
token = current_request.params[RUNNER_TOKEN_PARAM].presence
return unless token
::Ci::Runner.find_by_token(token) || raise(UnauthorizedError)
end
2018-03-17 18:26:18 +05:30
def validate_access_token!(scopes: [])
2020-07-28 23:09:34 +05:30
# return early if we've already authenticated via a job token
return if @current_authenticated_job.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
2020-05-24 23:13:21 +05:30
# return early if we've already authenticated via a deploy token
return if @current_authenticated_deploy_token.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
2018-03-17 18:26:18 +05:30
return unless access_token
case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
when AccessTokenValidationService::INSUFFICIENT_SCOPE
2021-06-08 01:23:25 +05:30
raise InsufficientScopeError, scopes
2018-03-17 18:26:18 +05:30
when AccessTokenValidationService::EXPIRED
raise ExpiredError
when AccessTokenValidationService::REVOKED
raise RevokedError
2019-02-15 15:39:39 +05:30
when AccessTokenValidationService::IMPERSONATION_DISABLED
raise ImpersonationDisabled
2018-03-17 18:26:18 +05:30
end
end
private
2020-07-28 23:09:34 +05:30
def find_user_from_job_bearer_token
return unless route_authentication_setting[:job_token_allowed]
token = parsed_oauth_token
return unless token
2020-09-03 11:15:55 +05:30
job = ::Ci::AuthJobFinder.new(token: token).execute
2020-07-28 23:09:34 +05:30
return unless job
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
job.user
end
2018-11-08 19:23:39 +05:30
def route_authentication_setting
return {} unless respond_to?(:route_setting)
route_setting(:authentication) || {}
end
2018-03-17 18:26:18 +05:30
def access_token
strong_memoize(:access_token) do
2021-03-08 18:12:59 +05:30
if try(:namespace_inheritable, :authentication)
access_token_from_namespace_inheritable
else
# The token can be a PAT or an OAuth (doorkeeper) token
# It is also possible that a PAT is encapsulated in a `Bearer` OAuth token
# (e.g. NPM client registry auth), this case will be properly handled
# by find_personal_access_token
find_oauth_access_token || find_personal_access_token
end
2018-03-17 18:26:18 +05:30
end
end
def find_personal_access_token
token =
current_request.params[PRIVATE_TOKEN_PARAM].presence ||
2019-10-12 21:52:04 +05:30
current_request.env[PRIVATE_TOKEN_HEADER].presence ||
parsed_oauth_token
2018-03-17 18:26:18 +05:30
return unless token
# Expiration, revocation and scopes are verified in `validate_access_token!`
2018-11-18 11:00:15 +05:30
PersonalAccessToken.find_by_token(token) || raise(UnauthorizedError)
2018-03-17 18:26:18 +05:30
end
def find_oauth_access_token
2019-10-12 21:52:04 +05:30
token = parsed_oauth_token
2018-03-17 18:26:18 +05:30
return unless token
2019-10-12 21:52:04 +05:30
# PATs with OAuth headers are not handled by OauthAccessToken
return if matches_personal_access_token_length?(token)
2018-03-17 18:26:18 +05:30
# Expiration, revocation and scopes are verified in `validate_access_token!`
oauth_token = OauthAccessToken.by_token(token)
raise UnauthorizedError unless oauth_token
oauth_token.revoke_previous_refresh_token!
oauth_token
end
2020-04-22 19:07:51 +05:30
def find_personal_access_token_from_http_basic_auth
return unless route_authentication_setting[:basic_auth_personal_access_token]
return unless has_basic_credentials?(current_request)
_username, password = user_name_and_password(current_request)
PersonalAccessToken.find_by_token(password)
end
2019-10-12 21:52:04 +05:30
def parsed_oauth_token
Doorkeeper::OAuth::Token.from_request(current_request, *Doorkeeper.configuration.access_token_methods)
end
def matches_personal_access_token_length?(token)
2021-02-22 17:27:13 +05:30
PersonalAccessToken::TOKEN_LENGTH_RANGE.include?(token.length)
2019-10-12 21:52:04 +05:30
end
2018-03-17 18:26:18 +05:30
# Check if the request is GET/HEAD, or if CSRF token is valid.
def verified_request?
Gitlab::RequestForgeryProtection.verified?(current_request.env)
end
def ensure_action_dispatch_request(request)
ActionDispatch::Request.new(request.env.dup)
end
def current_request
@current_request ||= ensure_action_dispatch_request(request)
end
2018-11-08 19:23:39 +05:30
2018-11-29 20:51:05 +05:30
def valid_web_access_format?(request_format)
case request_format
when :rss
rss_request?
when :ics
ics_request?
when :api
api_request?
2021-09-30 23:02:18 +05:30
when :archive
2021-10-27 15:23:28 +05:30
archive_request?
2018-11-29 20:51:05 +05:30
end
end
def valid_rss_format?(request_format)
case request_format
when :rss
rss_request?
when :ics
ics_request?
end
end
2019-12-04 20:38:33 +05:30
def valid_static_objects_format?(request_format)
case request_format
when :archive
archive_request?
2020-01-01 13:55:28 +05:30
when :blob
blob_request?
2019-12-04 20:38:33 +05:30
else
false
end
end
2018-11-08 19:23:39 +05:30
def rss_request?
current_request.path.ends_with?('.atom') || current_request.format.atom?
end
def ics_request?
current_request.path.ends_with?('.ics') || current_request.format.ics?
end
2018-11-29 20:51:05 +05:30
def api_request?
2021-01-03 14:25:43 +05:30
current_request.path.starts_with?(Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/api/'))
2018-11-29 20:51:05 +05:30
end
2019-12-04 20:38:33 +05:30
2021-09-30 23:02:18 +05:30
def git_request?
Gitlab::PathRegex.repository_git_route_regex.match?(current_request.path)
end
def git_lfs_request?
Gitlab::PathRegex.repository_git_lfs_route_regex.match?(current_request.path)
end
def git_or_lfs_request?
git_request? || git_lfs_request?
end
2019-12-04 20:38:33 +05:30
def archive_request?
current_request.path.include?('/-/archive/')
end
2020-01-01 13:55:28 +05:30
def blob_request?
current_request.path.include?('/raw/')
end
2020-09-03 11:15:55 +05:30
def find_valid_running_job_by_token!(token)
::Ci::AuthJobFinder.new(token: token).execute.tap do |job|
raise UnauthorizedError unless job
end
end
2018-03-17 18:26:18 +05:30
end
end
end
2020-05-24 23:13:21 +05:30
2021-06-08 01:23:25 +05:30
Gitlab::Auth::AuthFinders.prepend_mod_with('Gitlab::Auth::AuthFinders')