debian-mirror-gitlab/lib/gitlab/middleware/go.rb

155 lines
5.9 KiB
Ruby
Raw Normal View History

2019-02-15 15:39:39 +05:30
# frozen_string_literal: true
2016-06-02 11:05:42 +05:30
# A dumb middleware that returns a Go HTML document if the go-get=1 query string
# is used irrespective if the namespace/project exists
module Gitlab
module Middleware
class Go
2017-09-10 17:25:29 +05:30
include ActionView::Helpers::TagHelper
2019-02-15 15:39:39 +05:30
include ActionController::HttpAuthentication::Basic
2017-09-10 17:25:29 +05:30
PROJECT_PATH_REGEX = %r{\A(#{Gitlab::PathRegex.full_namespace_route_regex}/#{Gitlab::PathRegex.project_route_regex})/}.freeze
2016-06-02 11:05:42 +05:30
def initialize(app)
@app = app
end
def call(env)
2019-02-15 15:39:39 +05:30
request = ActionDispatch::Request.new(env)
2016-06-02 11:05:42 +05:30
2017-09-10 17:25:29 +05:30
render_go_doc(request) || @app.call(env)
2021-01-03 14:25:43 +05:30
rescue Gitlab::Auth::IpBlacklisted
Gitlab::AuthLogger.error(
message: 'Rack_Attack',
env: :blocklist,
remote_ip: request.ip,
request_method: request.request_method,
path: request.fullpath
)
Rack::Response.new('', 403).finish
2021-12-11 22:18:48 +05:30
rescue Gitlab::Auth::MissingPersonalAccessTokenError
Rack::Response.new('', 401).finish
2016-06-02 11:05:42 +05:30
end
private
def render_go_doc(request)
2017-09-10 17:25:29 +05:30
return unless go_request?(request)
2020-01-01 13:55:28 +05:30
path, branch = project_path(request)
2017-09-10 17:25:29 +05:30
return unless path
2020-01-01 13:55:28 +05:30
body, code = go_response(path, branch)
2017-09-10 17:25:29 +05:30
return unless body
2020-01-01 13:55:28 +05:30
response = Rack::Response.new(body, code, { 'Content-Type' => 'text/html' })
2016-06-02 11:05:42 +05:30
response.finish
end
def go_request?(request)
request["go-get"].to_i == 1 && request.env["PATH_INFO"].present?
end
2020-01-01 13:55:28 +05:30
def go_response(path, branch)
2018-03-17 18:26:18 +05:30
config = Gitlab.config
2020-01-01 13:55:28 +05:30
body_tag = content_tag :body, "go get #{config.gitlab.url}/#{path}"
unless branch
html_tag = content_tag :html, body_tag
return html_tag, 404
end
2019-02-15 15:39:39 +05:30
project_url = Gitlab::Utils.append_path(config.gitlab.url, path)
2017-08-17 22:00:37 +05:30
import_prefix = strip_url(project_url.to_s)
2016-06-02 11:05:42 +05:30
2018-03-17 18:26:18 +05:30
repository_url = if Gitlab::CurrentSettings.enabled_git_access_protocol == 'ssh'
shell = config.gitlab_shell
2020-04-08 14:13:33 +05:30
user = "#{shell.ssh_user}@" unless shell.ssh_user.empty?
2018-03-17 18:26:18 +05:30
port = ":#{shell.ssh_port}" unless shell.ssh_port == 22
2020-04-08 14:13:33 +05:30
"ssh://#{user}#{shell.ssh_host}#{port}/#{path}.git"
2018-03-17 18:26:18 +05:30
else
"#{project_url}.git"
end
2020-01-01 13:55:28 +05:30
meta_import_tag = tag :meta, name: 'go-import', content: "#{import_prefix} git #{repository_url}"
2020-03-13 15:44:24 +05:30
meta_source_tag = tag :meta, name: 'go-source', content: "#{import_prefix} #{project_url} #{project_url}/-/tree/#{branch}{/dir} #{project_url}/-/blob/#{branch}{/dir}/{file}#L{line}"
2020-01-01 13:55:28 +05:30
head_tag = content_tag :head, meta_import_tag + meta_source_tag
html_tag = content_tag :html, head_tag + body_tag
[html_tag, 200]
2016-06-02 11:05:42 +05:30
end
def strip_url(url)
2018-03-17 18:26:18 +05:30
url.gsub(%r{\Ahttps?://}, '')
2016-06-02 11:05:42 +05:30
end
2017-08-17 22:00:37 +05:30
def project_path(request)
path_info = request.env["PATH_INFO"]
2018-03-17 18:26:18 +05:30
path_info.sub!(%r{^/}, '')
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
project_path_match = "#{path_info}/".match(PROJECT_PATH_REGEX)
return unless project_path_match
2018-03-17 18:26:18 +05:30
2017-09-10 17:25:29 +05:30
path = project_path_match[1]
2017-08-17 22:00:37 +05:30
# Go subpackages may be in the form of `namespace/project/path1/path2/../pathN`.
# In a traditional project with a single namespace, this would denote repo
# `namespace/project` with subpath `path1/path2/../pathN`, but with nested
# groups, this could also be `namespace/project/path1` with subpath
# `path2/../pathN`, for example.
# We find all potential project paths out of the path segments
2017-09-10 17:25:29 +05:30
path_segments = path.split('/')
2017-08-17 22:00:37 +05:30
simple_project_path = path_segments.first(2).join('/')
project_paths = []
begin
project_paths << path_segments.join('/')
path_segments.pop
end while path_segments.length >= 2
# We see if a project exists with any of these potential paths
project = project_for_paths(project_paths, request)
if project
# If a project is found and the user has access, we return the full project path
2020-07-28 23:09:34 +05:30
[project.full_path, project.default_branch]
2017-08-17 22:00:37 +05:30
else
# If not, we return the first two components as if it were a simple `namespace/project` path,
# so that we don't reveal the existence of a nested project the user doesn't have access to.
# This means that for an unauthenticated request to `group/subgroup/project/subpackage`
# for a private `group/subgroup/project` with subpackage path `subpackage`, GitLab will respond
# as if the user is looking for project `group/subgroup`, with subpackage path `project/subpackage`.
# Since `go get` doesn't authenticate by default, this means that
# `go get gitlab.com/group/subgroup/project/subpackage` will not work for private projects.
# `go get gitlab.com/group/subgroup/project.git/subpackage` will work, since Go is smart enough
# to figure that out. `import 'gitlab.com/...'` behaves the same as `go get`.
2020-07-28 23:09:34 +05:30
[simple_project_path, 'master']
2017-08-17 22:00:37 +05:30
end
end
def project_for_paths(paths, request)
project = Project.where_full_path_in(paths).first
2021-10-27 15:23:28 +05:30
return unless authentication_result(request, project).can_perform_action_on_project?(:read_project, project)
2017-08-17 22:00:37 +05:30
project
end
2021-10-27 15:23:28 +05:30
def authentication_result(request, project)
empty_result = Gitlab::Auth::Result::EMPTY
return empty_result unless has_basic_credentials?(request)
2019-02-15 15:39:39 +05:30
login, password = user_name_and_password(request)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
2021-10-27 15:23:28 +05:30
return empty_result unless auth_result.success?
2018-03-27 19:54:05 +05:30
2021-10-27 15:23:28 +05:30
return empty_result unless auth_result.can?(:access_git)
2018-03-27 19:54:05 +05:30
2021-10-27 15:23:28 +05:30
return empty_result unless auth_result.authentication_abilities_include?(:read_project)
2018-03-27 19:54:05 +05:30
2021-10-27 15:23:28 +05:30
auth_result
2017-08-17 22:00:37 +05:30
end
2016-06-02 11:05:42 +05:30
end
end
end