1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
require_relative 'shell_env'
module Grack
class Auth < Rack::Auth::Basic
attr_accessor :user, :project, :env
def call(env)
@env = env
@request = Rack::Request.new(env)
@auth = Request.new(env)
# Need this patch due to the rails mount
# Need this if under RELATIVE_URL_ROOT
unless Gitlab.config.gitlab.relative_url_root.empty?
# If website is mounted using relative_url_root need to remove it first
@env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root,'')
else
@env['PATH_INFO'] = @request.path
end
@env['SCRIPT_NAME'] = ""
if project
auth!
else
render_not_found
end
end
private
def auth!
if @auth.provided?
return bad_request unless @auth.basic?
# Authentication with username and password
login, password = @auth.credentials
# Allow authentication for GitLab CI service
# if valid token passed
if gitlab_ci_request?(login, password)
return @app.call(env)
end
@user = authenticate_user(login, password)
if @user
Gitlab::ShellEnv.set_env(@user)
@env['REMOTE_USER'] = @auth.username
end
end
if authorized_request?
@app.call(env)
else
unauthorized
end
end
def gitlab_ci_request?(login, password)
if login == "gitlab-ci-token" && project.gitlab_ci?
token = project.gitlab_ci_service.token
if token.present? && token == password && git_cmd == 'git-upload-pack'
return true
end
end
false
end
def oauth_access_token_check(login, password)
if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present?
token = Doorkeeper::AccessToken.by_token(password)
token && token.accessible? && User.find_by(id: token.resource_owner_id)
end
end
def authenticate_user(login, password)
user = Gitlab::Auth.new.find(login, password)
unless user
user = oauth_access_token_check(login, password)
end
return user if user.present?
# At this point, we know the credentials were wrong. We let Rack::Attack
# know there was a failed authentication attempt from this IP. This
# information is stored in the Rails cache (Redis) and will be used by
# the Rack::Attack middleware to decide whether to block requests from
# this IP.
config = Gitlab.config.rack_attack.git_basic_auth
Rack::Attack::Allow2Ban.filter(@request.ip, config) do
# Unless the IP is whitelisted, return true so that Allow2Ban
# increments the counter (stored in Rails.cache) for the IP
if config.ip_whitelist.include?(@request.ip)
false
else
true
end
end
nil # No user was found
end
def authorized_request?
case git_cmd
when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
if user
Gitlab::GitAccess.new.download_access_check(user, project).allowed?
elsif project.public?
# Allow clone/fetch for public projects
true
else
false
end
when *Gitlab::GitAccess::PUSH_COMMANDS
if user
# Skip user authorization on upload request.
# It will be done by the pre-receive hook in the repository.
true
else
false
end
else
false
end
end
def git_cmd
if @request.get?
@request.params['service']
elsif @request.post?
File.basename(@request.path)
else
nil
end
end
def project
@project ||= project_by_path(@request.path_info)
end
def project_by_path(path)
if m = /^([\w\.\/-]+)\.git/.match(path).to_a
path_with_namespace = m.last
path_with_namespace.gsub!(/\.wiki$/, '')
Project.find_with_namespace(path_with_namespace)
end
end
def render_not_found
[404, {"Content-Type" => "text/plain"}, ["Not Found"]]
end
end
end
|