7f00bcb92e
GitLab has a mechanism that allows CI to clone repositories via HTTP even when the HTTP protocol is disabled. This works as expected when a project is private or internal. However, when a project is public CI gets an error message that HTTP is not allowed. This happens because Git only sends auth in a subsequent request after a 401 is returned first. For public projects, GitLab grabs onto that unauthenticated request and sends it through since it recognizes that Guests are ordinarily allowed to access the repository. Later on this leads to a 403 since HTTP protocol is disabled. Fix this by only continuing with unauthenticated requests when HTTP is allowed.
121 lines
3.3 KiB
Ruby
121 lines
3.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# This file should be identical in GitLab Community Edition and Enterprise Edition
|
|
|
|
class Projects::GitHttpClientController < Projects::ApplicationController
|
|
include ActionController::HttpAuthentication::Basic
|
|
include KerberosSpnegoHelper
|
|
|
|
attr_reader :authentication_result, :redirected_path
|
|
|
|
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
|
|
delegate :type, to: :authentication_result, allow_nil: true, prefix: :auth_result
|
|
|
|
alias_method :user, :actor
|
|
alias_method :authenticated_user, :actor
|
|
|
|
# Git clients will not know what authenticity token to send along
|
|
skip_around_action :set_session_storage
|
|
skip_before_action :verify_authenticity_token
|
|
skip_before_action :repository
|
|
before_action :authenticate_user
|
|
|
|
private
|
|
|
|
def download_request?
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def upload_request?
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def authenticate_user
|
|
@authentication_result = Gitlab::Auth::Result.new
|
|
|
|
if allow_basic_auth? && basic_auth_provided?
|
|
login, password = user_name_and_password(request)
|
|
|
|
if handle_basic_authentication(login, password)
|
|
return # Allow access
|
|
end
|
|
elsif allow_kerberos_spnego_auth? && spnego_provided?
|
|
kerberos_user = find_kerberos_user
|
|
|
|
if kerberos_user
|
|
@authentication_result = Gitlab::Auth::Result.new(
|
|
kerberos_user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities)
|
|
|
|
send_final_spnego_response
|
|
return # Allow access
|
|
end
|
|
elsif project && download_request? && http_allowed? && Guest.can?(:download_code, project)
|
|
|
|
@authentication_result = Gitlab::Auth::Result.new(nil, project, :none, [:download_code])
|
|
|
|
return # Allow access
|
|
end
|
|
|
|
send_challenges
|
|
render plain: "HTTP Basic: Access denied\n", status: :unauthorized
|
|
rescue Gitlab::Auth::MissingPersonalAccessTokenError
|
|
render_missing_personal_access_token
|
|
end
|
|
|
|
def basic_auth_provided?
|
|
has_basic_credentials?(request)
|
|
end
|
|
|
|
def send_challenges
|
|
challenges = []
|
|
challenges << 'Basic realm="GitLab"' if allow_basic_auth?
|
|
challenges << spnego_challenge if allow_kerberos_spnego_auth?
|
|
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
|
|
end
|
|
|
|
def project
|
|
parse_repo_path unless defined?(@project)
|
|
|
|
@project
|
|
end
|
|
|
|
def parse_repo_path
|
|
@project, @repo_type, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
|
|
end
|
|
|
|
def render_missing_personal_access_token
|
|
render plain: "HTTP Basic: Access denied\n" \
|
|
"You must use a personal access token with 'read_repository' or 'write_repository' scope for Git over HTTP.\n" \
|
|
"You can generate one at #{profile_personal_access_tokens_url}",
|
|
status: :unauthorized
|
|
end
|
|
|
|
def repository
|
|
repo_type.repository_for(project)
|
|
end
|
|
|
|
def wiki?
|
|
repo_type.wiki?
|
|
end
|
|
|
|
def repo_type
|
|
parse_repo_path unless defined?(@repo_type)
|
|
|
|
@repo_type
|
|
end
|
|
|
|
def handle_basic_authentication(login, password)
|
|
@authentication_result = Gitlab::Auth.find_for_git_client(
|
|
login, password, project: project, ip: request.ip)
|
|
|
|
@authentication_result.success?
|
|
end
|
|
|
|
def ci?
|
|
authentication_result.ci?(project)
|
|
end
|
|
|
|
def http_allowed?
|
|
Gitlab::ProtocolAccess.allowed?('http')
|
|
end
|
|
end
|