debian-mirror-gitlab/lib/api/internal/base.rb

333 lines
12 KiB
Ruby
Raw Normal View History

2019-12-04 20:38:33 +05:30
# frozen_string_literal: true
module API
# Internal access API
module Internal
2021-01-03 14:25:43 +05:30
class Base < ::API::Base
2019-12-04 20:38:33 +05:30
before { authenticate_by_gitlab_shell_token! }
2020-03-13 15:44:24 +05:30
before do
2021-01-29 00:20:46 +05:30
api_endpoint = env['api.endpoint']
feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s
header[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category
2020-03-13 15:44:24 +05:30
Gitlab::ApplicationContext.push(
user: -> { actor&.user },
project: -> { project },
2021-01-29 00:20:46 +05:30
caller_id: route.origin,
2021-03-08 18:12:59 +05:30
remote_ip: request.ip,
2021-01-29 00:20:46 +05:30
feature_category: feature_category
2020-03-13 15:44:24 +05:30
)
end
2019-12-04 20:38:33 +05:30
helpers ::API::Helpers::InternalHelpers
UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'.freeze
2020-10-24 23:57:45 +05:30
VALID_PAT_SCOPES = Set.new(
Gitlab::Auth::API_SCOPES + Gitlab::Auth::REPOSITORY_SCOPES + Gitlab::Auth::REGISTRY_SCOPES
).freeze
2019-12-04 20:38:33 +05:30
helpers do
def response_with_status(code: 200, success: true, message: nil, **extra_options)
status code
{ status: success, message: message }.merge(extra_options).compact
end
2021-01-29 00:20:46 +05:30
def lfs_authentication_url(container)
2019-12-04 20:38:33 +05:30
# This is a separate method so that EE can alter its behaviour more
# easily.
2021-01-29 00:20:46 +05:30
container.lfs_http_url_to_repo
2019-12-04 20:38:33 +05:30
end
2019-12-21 20:55:43 +05:30
def check_allowed(params)
# This is a separate method so that EE can alter its behaviour more
# easily.
2019-12-04 20:38:33 +05:30
# Stores some Git-specific env thread-safely
env = parse_env
2020-04-08 14:13:33 +05:30
Gitlab::Git::HookEnv.set(gl_repository, env) if container
2019-12-04 20:38:33 +05:30
actor.update_last_used_at!
check_result = begin
2020-04-25 10:58:03 +05:30
access_check!(actor, params)
2020-04-08 14:13:33 +05:30
rescue Gitlab::GitAccess::ForbiddenError => e
# The return code needs to be 401. If we return 403
# the custom message we return won't be shown to the user
# and, instead, the default message 'GitLab: API is not accessible'
# will be displayed
2019-12-21 20:55:43 +05:30
return response_with_status(code: 401, success: false, message: e.message)
2019-12-04 20:38:33 +05:30
rescue Gitlab::GitAccess::TimeoutError => e
2019-12-21 20:55:43 +05:30
return response_with_status(code: 503, success: false, message: e.message)
2019-12-04 20:38:33 +05:30
rescue Gitlab::GitAccess::NotFoundError => e
2019-12-21 20:55:43 +05:30
return response_with_status(code: 404, success: false, message: e.message)
2019-12-04 20:38:33 +05:30
end
log_user_activity(actor.user)
case check_result
when ::Gitlab::GitAccessResult::Success
payload = {
gl_repository: gl_repository,
2020-04-08 14:13:33 +05:30
gl_project_path: gl_repository_path,
2019-12-04 20:38:33 +05:30
gl_id: Gitlab::GlId.gl_id(actor.user),
gl_username: actor.username,
2020-07-28 23:09:34 +05:30
git_config_options: ["uploadpack.allowFilter=true",
"uploadpack.allowAnySHA1InWant=true"],
2019-12-04 20:38:33 +05:30
gitaly: gitaly_payload(params[:action]),
gl_console_messages: check_result.console_messages
2020-10-24 23:57:45 +05:30
}.merge!(actor.key_details)
2019-12-04 20:38:33 +05:30
# Custom option for git-receive-pack command
2020-05-24 23:13:21 +05:30
2019-12-04 20:38:33 +05:30
receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
2020-05-24 23:13:21 +05:30
2019-12-04 20:38:33 +05:30
if receive_max_input_size > 0
payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}"
end
response_with_status(**payload)
when ::Gitlab::GitAccessResult::CustomAction
2019-12-26 22:10:19 +05:30
response_with_status(code: 300, payload: check_result.payload, gl_console_messages: check_result.console_messages)
2019-12-04 20:38:33 +05:30
else
response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR)
end
end
2020-04-25 10:58:03 +05:30
def access_check!(actor, params)
access_checker = access_checker_for(actor, params[:protocol])
access_checker.check(params[:action], params[:changes]).tap do |result|
break result if @project || !repo_type.project?
# If we have created a project directly from a git push
# we have to assign its value to both @project and @container
2020-10-24 23:57:45 +05:30
@project = @container = access_checker.container
2020-04-25 10:58:03 +05:30
end
end
2021-01-03 14:25:43 +05:30
def validate_actor_key(actor, key_id)
return 'Could not find a user without a key' unless key_id
return 'Could not find the given key' unless actor.key
'Could not find a user for the given key' unless actor.user
end
2019-12-21 20:55:43 +05:30
end
namespace 'internal' do
# Check if git command is allowed for project
#
# Params:
# key_id - ssh key id for Git over SSH
# user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode
# username - user name for Git over SSH in keyless SSH cert mode
# protocol - Git access protocol being used, e.g. HTTP or SSH
# project - project full_path (not path on disk)
# action - git action (git-upload-pack or git-receive-pack)
# changes - changes as "oldrev newrev ref", see Gitlab::ChangesList
# check_ip - optional, only in EE version, may limit access to
# group resources based on its IP restrictions
2021-01-29 00:20:46 +05:30
post "/allowed", feature_category: :source_code_management do
2019-12-21 20:55:43 +05:30
# It was moved to a separate method so that EE can alter its behaviour more
# easily.
check_allowed(params)
end
2019-12-04 20:38:33 +05:30
2021-01-29 00:20:46 +05:30
post "/lfs_authenticate", feature_category: :source_code_management do
not_found! unless container&.lfs_enabled?
2019-12-04 20:38:33 +05:30
status 200
2020-01-01 13:55:28 +05:30
unless actor.key_or_user
raise ActiveRecord::RecordNotFound.new('User not found!')
2019-12-04 20:38:33 +05:30
end
2020-01-01 13:55:28 +05:30
actor.update_last_used_at!
2019-12-04 20:38:33 +05:30
Gitlab::LfsToken
2020-01-01 13:55:28 +05:30
.new(actor.key_or_user)
2021-01-29 00:20:46 +05:30
.authentication_payload(lfs_authentication_url(container))
2019-12-04 20:38:33 +05:30
end
#
# Get a ssh key using the fingerprint
#
# rubocop: disable CodeReuse/ActiveRecord
2021-01-29 00:20:46 +05:30
get '/authorized_keys', feature_category: :source_code_management do
2019-12-04 20:38:33 +05:30
fingerprint = params.fetch(:fingerprint) do
Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint
end
key = Key.find_by(fingerprint: fingerprint)
2020-01-01 13:55:28 +05:30
not_found!('Key') if key.nil?
2019-12-04 20:38:33 +05:30
present key, with: Entities::SSHKey
end
# rubocop: enable CodeReuse/ActiveRecord
#
# Discover user by ssh key, user id or username
#
2021-01-29 00:20:46 +05:30
get '/discover', feature_category: :authentication_and_authorization do
2020-01-01 13:55:28 +05:30
present actor.user, with: Entities::UserSafe
2019-12-04 20:38:33 +05:30
end
2021-01-29 00:20:46 +05:30
get '/check', feature_category: :not_owned do
2019-12-04 20:38:33 +05:30
{
api_version: API.version,
gitlab_version: Gitlab::VERSION,
gitlab_rev: Gitlab.revision,
redis: redis_ping
}
end
2021-01-03 14:25:43 +05:30
2021-01-29 00:20:46 +05:30
post '/two_factor_recovery_codes', feature_category: :authentication_and_authorization do
2019-12-04 20:38:33 +05:30
status 200
2020-01-01 13:55:28 +05:30
actor.update_last_used_at!
user = actor.user
2019-12-04 20:38:33 +05:30
2021-01-03 14:25:43 +05:30
error_message = validate_actor_key(actor, params[:key_id])
2019-12-04 20:38:33 +05:30
2021-01-03 14:25:43 +05:30
if params[:user_id] && user.nil?
2020-01-01 13:55:28 +05:30
break { success: false, message: 'Could not find the given user' }
2021-01-03 14:25:43 +05:30
elsif error_message
break { success: false, message: error_message }
2019-12-04 20:38:33 +05:30
end
2021-01-03 14:25:43 +05:30
break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' } if actor.key.is_a?(DeployKey)
2019-12-04 20:38:33 +05:30
unless user.two_factor_enabled?
break { success: false, message: 'Two-factor authentication is not enabled for this user' }
end
codes = nil
::Users::UpdateService.new(current_user, user: user).execute! do |user|
codes = user.generate_otp_backup_codes!
end
{ success: true, recovery_codes: codes }
end
2021-01-29 00:20:46 +05:30
post '/personal_access_token', feature_category: :authentication_and_authorization do
2020-10-24 23:57:45 +05:30
status 200
actor.update_last_used_at!
user = actor.user
2021-01-03 14:25:43 +05:30
error_message = validate_actor_key(actor, params[:key_id])
2020-10-24 23:57:45 +05:30
2021-01-03 14:25:43 +05:30
break { success: false, message: 'Deploy keys cannot be used to create personal access tokens' } if actor.key.is_a?(DeployKey)
2020-10-24 23:57:45 +05:30
2021-01-03 14:25:43 +05:30
if params[:user_id] && user.nil?
2020-10-24 23:57:45 +05:30
break { success: false, message: 'Could not find the given user' }
2021-01-03 14:25:43 +05:30
elsif error_message
break { success: false, message: error_message }
2020-10-24 23:57:45 +05:30
end
if params[:name].blank?
break { success: false, message: "No token name specified" }
end
if params[:scopes].blank?
break { success: false, message: "No token scopes specified" }
end
invalid_scope = params[:scopes].find { |scope| VALID_PAT_SCOPES.exclude?(scope.to_sym) }
if invalid_scope
valid_scopes = VALID_PAT_SCOPES.map(&:to_s).sort
break { success: false, message: "Invalid scope: '#{invalid_scope}'. Valid scopes are: #{valid_scopes}" }
end
begin
expires_at = params[:expires_at].presence && Date.parse(params[:expires_at])
rescue ArgumentError
break { success: false, message: "Invalid token expiry date: '#{params[:expires_at]}'" }
end
2020-11-24 15:15:51 +05:30
result = ::PersonalAccessTokens::CreateService.new(
2021-01-29 00:20:46 +05:30
current_user: user, target_user: user, params: { name: params[:name], scopes: params[:scopes], expires_at: expires_at }
2020-11-24 15:15:51 +05:30
).execute
2020-10-24 23:57:45 +05:30
2020-11-24 15:15:51 +05:30
unless result.status == :success
break { success: false, message: "Failed to create token: #{result.message}" }
2020-10-24 23:57:45 +05:30
end
2020-11-24 15:15:51 +05:30
access_token = result.payload[:personal_access_token]
2020-10-24 23:57:45 +05:30
{ success: true, token: access_token.token, scopes: access_token.scopes, expires_at: access_token.expires_at }
end
2021-01-29 00:20:46 +05:30
post '/pre_receive', feature_category: :source_code_management do
2019-12-04 20:38:33 +05:30
status 200
reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase
{ reference_counter_increased: reference_counter_increased }
end
2021-01-29 00:20:46 +05:30
post '/post_receive', feature_category: :source_code_management do
2019-12-04 20:38:33 +05:30
status 200
2020-04-08 14:13:33 +05:30
response = PostReceiveService.new(actor.user, repository, project, params).execute
2019-12-04 20:38:33 +05:30
present response, with: Entities::InternalPostReceive::Response
end
2021-01-03 14:25:43 +05:30
2021-01-29 00:20:46 +05:30
post '/two_factor_config', feature_category: :authentication_and_authorization do
2021-01-03 14:25:43 +05:30
status 200
break { success: false } unless Feature.enabled?(:two_factor_for_cli)
actor.update_last_used_at!
user = actor.user
error_message = validate_actor_key(actor, params[:key_id])
if error_message
{ success: false, message: error_message }
elsif actor.key.is_a?(DeployKey)
{ success: true, two_factor_required: false }
else
{
success: true,
two_factor_required: user.two_factor_enabled?
}
end
end
2021-01-29 00:20:46 +05:30
post '/two_factor_otp_check', feature_category: :authentication_and_authorization do
2021-01-03 14:25:43 +05:30
status 200
2021-02-22 17:27:13 +05:30
break { success: false, message: 'Feature flag is disabled' } unless Feature.enabled?(:two_factor_for_cli)
2021-01-03 14:25:43 +05:30
actor.update_last_used_at!
user = actor.user
error_message = validate_actor_key(actor, params[:key_id])
break { success: false, message: error_message } if error_message
break { success: false, message: 'Deploy keys cannot be used for Two Factor' } if actor.key.is_a?(DeployKey)
break { success: false, message: 'Two-factor authentication is not enabled for this user' } unless user.two_factor_enabled?
otp_validation_result = ::Users::ValidateOtpService.new(user).execute(params.fetch(:otp_attempt))
if otp_validation_result[:status] == :success
2021-02-22 17:27:13 +05:30
::Gitlab::Auth::Otp::SessionEnforcer.new(actor.key).update_session
2021-01-03 14:25:43 +05:30
{ success: true }
else
{ success: false, message: 'Invalid OTP' }
end
end
2019-12-04 20:38:33 +05:30
end
end
end
end
API::Internal::Base.prepend_if_ee('EE::API::Internal::Base')