Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
0d975244
Commit
0d975244
authored
Jul 20, 2016
by
Jacob Vosmaer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add LFS controllers
parent
033e5423
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
420 additions
and
116 deletions
+420
-116
app/controllers/projects/git_http_client_controller.rb
app/controllers/projects/git_http_client_controller.rb
+110
-0
app/controllers/projects/git_http_controller.rb
app/controllers/projects/git_http_controller.rb
+3
-104
app/controllers/projects/lfs_api_controller.rb
app/controllers/projects/lfs_api_controller.rb
+94
-0
app/controllers/projects/lfs_storage_controller.rb
app/controllers/projects/lfs_storage_controller.rb
+106
-0
app/helpers/lfs_helper.rb
app/helpers/lfs_helper.rb
+66
-0
config/routes.rb
config/routes.rb
+16
-4
spec/requests/lfs_http_spec.rb
spec/requests/lfs_http_spec.rb
+25
-8
No files found.
app/controllers/projects/git_http_client_controller.rb
0 → 100644
View file @
0d975244
# 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
:user
# Git clients will not know what authenticity token to send along
skip_before_action
:verify_authenticity_token
skip_before_action
:repository
before_action
:authenticate_user
before_action
:ensure_project_found!
private
def
authenticate_user
if
project
&&
project
.
public?
&&
download_request?
return
# Allow access
end
if
allow_basic_auth?
&&
basic_auth_provided?
login
,
password
=
user_name_and_password
(
request
)
auth_result
=
Gitlab
::
Auth
.
find_for_git_client
(
login
,
password
,
project:
project
,
ip:
request
.
ip
)
if
auth_result
.
type
==
:ci
&&
download_request?
@ci
=
true
elsif
auth_result
.
type
==
:oauth
&&
!
download_request?
# Not allowed
else
@user
=
auth_result
.
user
end
if
ci?
||
user
return
# Allow access
end
elsif
allow_kerberos_spnego_auth?
&&
spnego_provided?
@user
=
find_kerberos_user
if
user
send_final_spnego_response
return
# Allow access
end
end
send_challenges
render
plain:
"HTTP Basic: Access denied
\n
"
,
status:
401
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
ensure_project_found!
render_not_found
if
project
.
blank?
end
def
project
return
@project
if
defined?
(
@project
)
project_id
,
_
=
project_id_with_suffix
if
project_id
.
blank?
@project
=
nil
else
@project
=
Project
.
find_with_namespace
(
"
#{
params
[
:namespace_id
]
}
/
#{
project_id
}
"
)
end
end
# This method returns two values so that we can parse
# params[:project_id] (untrusted input!) in exactly one place.
def
project_id_with_suffix
id
=
params
[
:project_id
]
||
''
%w[.wiki.git .git]
.
each
do
|
suffix
|
if
id
.
end_with?
(
suffix
)
# Be careful to only remove the suffix from the end of 'id'.
# Accidentally removing it from the middle is how security
# vulnerabilities happen!
return
[
id
.
slice
(
0
,
id
.
length
-
suffix
.
length
),
suffix
]
end
end
# Something is wrong with params[:project_id]; do not pass it on.
[
nil
,
nil
]
end
def
repository
_
,
suffix
=
project_id_with_suffix
if
suffix
==
'.wiki.git'
project
.
wiki
.
repository
else
project
.
repository
end
end
def
render_not_found
render
plain:
'Not Found'
,
status: :not_found
end
def
ci?
@ci
.
present?
end
end
app/controllers/projects/git_http_controller.rb
View file @
0d975244
# This file should be identical in GitLab Community Edition and Enterprise Edition
# This file should be identical in GitLab Community Edition and Enterprise Edition
class
Projects::GitHttpController
<
Projects
::
ApplicationController
class
Projects::GitHttpController
<
Projects
::
GitHttpClientController
include
ActionController
::
HttpAuthentication
::
Basic
include
KerberosSpnegoHelper
attr_reader
:user
# Git clients will not know what authenticity token to send along
skip_before_action
:verify_authenticity_token
skip_before_action
:repository
before_action
:authenticate_user
before_action
:ensure_project_found!
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
def
info_refs
def
info_refs
...
@@ -46,81 +35,8 @@ class Projects::GitHttpController < Projects::ApplicationController
...
@@ -46,81 +35,8 @@ class Projects::GitHttpController < Projects::ApplicationController
private
private
def
authenticate_user
def
download_request?
if
project
&&
project
.
public?
&&
upload_pack?
upload_pack?
return
# Allow access
end
if
allow_basic_auth?
&&
basic_auth_provided?
login
,
password
=
user_name_and_password
(
request
)
auth_result
=
Gitlab
::
Auth
.
find_for_git_client
(
login
,
password
,
project:
project
,
ip:
request
.
ip
)
if
auth_result
.
type
==
:ci
&&
upload_pack?
@ci
=
true
elsif
auth_result
.
type
==
:oauth
&&
!
upload_pack?
# Not allowed
else
@user
=
auth_result
.
user
end
if
ci?
||
user
return
# Allow access
end
elsif
allow_kerberos_spnego_auth?
&&
spnego_provided?
@user
=
find_kerberos_user
if
user
send_final_spnego_response
return
# Allow access
end
end
send_challenges
render
plain:
"HTTP Basic: Access denied
\n
"
,
status:
401
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
ensure_project_found!
render_not_found
if
project
.
blank?
end
def
project
return
@project
if
defined?
(
@project
)
project_id
,
_
=
project_id_with_suffix
if
project_id
.
blank?
@project
=
nil
else
@project
=
Project
.
find_with_namespace
(
"
#{
params
[
:namespace_id
]
}
/
#{
project_id
}
"
)
end
end
# This method returns two values so that we can parse
# params[:project_id] (untrusted input!) in exactly one place.
def
project_id_with_suffix
id
=
params
[
:project_id
]
||
''
%w[.wiki.git .git]
.
each
do
|
suffix
|
if
id
.
end_with?
(
suffix
)
# Be careful to only remove the suffix from the end of 'id'.
# Accidentally removing it from the middle is how security
# vulnerabilities happen!
return
[
id
.
slice
(
0
,
id
.
length
-
suffix
.
length
),
suffix
]
end
end
# Something is wrong with params[:project_id]; do not pass it on.
[
nil
,
nil
]
end
end
def
upload_pack?
def
upload_pack?
...
@@ -143,27 +59,10 @@ class Projects::GitHttpController < Projects::ApplicationController
...
@@ -143,27 +59,10 @@ class Projects::GitHttpController < Projects::ApplicationController
render
json:
Gitlab
::
Workhorse
.
git_http_ok
(
repository
,
user
)
render
json:
Gitlab
::
Workhorse
.
git_http_ok
(
repository
,
user
)
end
end
def
repository
_
,
suffix
=
project_id_with_suffix
if
suffix
==
'.wiki.git'
project
.
wiki
.
repository
else
project
.
repository
end
end
def
render_not_found
render
plain:
'Not Found'
,
status: :not_found
end
def
render_not_allowed
def
render_not_allowed
render
plain:
download_access
.
message
,
status: :forbidden
render
plain:
download_access
.
message
,
status: :forbidden
end
end
def
ci?
@ci
.
present?
end
def
upload_pack_allowed?
def
upload_pack_allowed?
return
false
unless
Gitlab
.
config
.
gitlab_shell
.
upload_pack
return
false
unless
Gitlab
.
config
.
gitlab_shell
.
upload_pack
...
...
app/controllers/projects/lfs_api_controller.rb
0 → 100644
View file @
0d975244
class
Projects::LfsApiController
<
Projects
::
GitHttpClientController
include
LfsHelper
before_action
:lfs_enabled!
before_action
:lfs_check_access!
,
except:
[
:deprecated
]
def
batch
unless
objects
.
present?
render_lfs_not_found
return
end
if
download_request?
render
json:
{
objects:
download_objects!
}
elsif
upload_request?
render
json:
{
objects:
upload_objects!
}
else
raise
"Never reached"
end
end
def
deprecated
render
(
json:
{
message:
'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.'
,
documentation_url:
"
#{
Gitlab
.
config
.
gitlab
.
url
}
/help"
,
},
status:
501
)
end
private
def
objects
(
params
[
:objects
]
||
[]).
to_a
end
def
existing_oids
@existing_oids
||=
begin
storage_project
.
lfs_objects
.
where
(
oid:
objects
.
map
{
|
o
|
o
[
'oid'
].
to_s
}).
pluck
(
:oid
)
end
end
def
download_objects!
objects
.
each
do
|
object
|
if
existing_oids
.
include?
(
object
[
:oid
])
object
[
:actions
]
=
download_actions
(
object
)
else
object
[
:error
]
=
{
code:
404
,
message:
"Object does not exist on the server or you don't have permissions to access it"
,
}
end
end
objects
end
def
upload_objects!
objects
.
each
do
|
object
|
object
[
:actions
]
=
upload_actions
(
object
)
unless
existing_oids
.
include?
(
object
[
:oid
])
end
objects
end
def
download_actions
(
object
)
{
download:
{
href:
"
#{
project
.
http_url_to_repo
}
/gitlab-lfs/objects/
#{
object
[
:oid
]
}
"
,
header:
{
Authorization
:
request
.
headers
[
'Authorization'
]
}.
compact
}
}
end
def
upload_actions
(
object
)
{
upload:
{
href:
"
#{
project
.
http_url_to_repo
}
/gitlab-lfs/objects/
#{
object
[
:oid
]
}
/
#{
object
[
:size
]
}
"
,
header:
{
Authorization
:
request
.
headers
[
'Authorization'
]
}.
compact
}
}
end
def
download_request?
params
[
:operation
]
==
'download'
end
def
upload_request?
params
[
:operation
]
==
'upload'
end
end
app/controllers/projects/lfs_storage_controller.rb
0 → 100644
View file @
0d975244
class
Projects::LfsStorageController
<
Projects
::
GitHttpClientController
include
LfsHelper
before_action
:lfs_enabled!
before_action
:lfs_check_access!
def
download
lfs_object
=
LfsObject
.
find_by_oid
(
oid
)
unless
lfs_object
&&
lfs_object
.
file
.
exists?
render_lfs_not_found
return
end
send_file
lfs_object
.
file
.
path
,
content_type:
"application/octet-stream"
end
def
upload_authorize
render
(
json:
{
StoreLFSPath
:
"
#{
Gitlab
.
config
.
lfs
.
storage_path
}
/tmp/upload"
,
LfsOid
:
oid
,
LfsSize
:
size
,
},
content_type:
'application/json; charset=utf-8'
)
end
def
upload_finalize
unless
tmp_filename
render_lfs_forbidden
return
end
if
store_file
(
oid
,
size
,
tmp_filename
)
head
200
else
render
plain:
'Unprocessable entity'
,
status:
422
end
end
private
def
download_request?
action_name
==
'download'
end
def
upload_request?
%w[upload_authorize upload_finalize]
.
include?
action_name
end
def
oid
params
[
:oid
].
to_s
end
def
size
params
[
:size
].
to_i
end
def
tmp_filename
name
=
request
.
headers
[
'X-Gitlab-Lfs-Tmp'
]
if
name
.
present?
name
.
gsub!
(
/^.*(\\|\/)/
,
''
)
name
=
name
.
match
(
/[0-9a-f]{73}/
)
name
[
0
]
if
name
else
nil
end
end
def
store_file
(
oid
,
size
,
tmp_file
)
tmp_file_path
=
File
.
join
(
"
#{
Gitlab
.
config
.
lfs
.
storage_path
}
/tmp/upload"
,
tmp_file
)
object
=
LfsObject
.
find_or_create_by
(
oid:
oid
,
size:
size
)
if
object
.
file
.
exists?
success
=
true
else
success
=
move_tmp_file_to_storage
(
object
,
tmp_file_path
)
end
if
success
success
=
link_to_project
(
object
)
end
success
ensure
# Ensure that the tmp file is removed
FileUtils
.
rm_f
(
tmp_file_path
)
end
def
move_tmp_file_to_storage
(
object
,
path
)
File
.
open
(
path
)
do
|
f
|
object
.
file
=
f
end
object
.
file
.
store!
object
.
save
end
def
link_to_project
(
object
)
if
object
&&
!
object
.
projects
.
exists?
(
storage_project
.
id
)
object
.
projects
<<
storage_project
object
.
save
end
end
end
app/helpers/lfs_helper.rb
0 → 100644
View file @
0d975244
module
LfsHelper
def
lfs_enabled!
return
if
Gitlab
.
config
.
lfs
.
enabled
render
(
json:
{
message:
'Git LFS is not enabled on this GitLab server, contact your admin.'
,
documentation_url:
"
#{
Gitlab
.
config
.
gitlab
.
url
}
/help"
,
},
status:
501
)
end
def
lfs_check_access!
return
if
download_request?
&&
lfs_download_access?
return
if
upload_request?
&&
lfs_upload_access?
if
project
.
public?
||
(
user
&&
user
.
can?
(
:read_project
,
project
))
render_lfs_forbidden
else
render_lfs_not_found
end
end
def
lfs_download_access?
project
.
public?
||
ci?
||
(
user
&&
user
.
can?
(
:download_code
,
project
))
end
def
lfs_upload_access?
user
&&
user
.
can?
(
:push_code
,
project
)
end
def
render_lfs_forbidden
render
(
json:
{
message:
'Access forbidden. Check your access level.'
,
documentation_url:
"
#{
Gitlab
.
config
.
gitlab
.
url
}
/help"
,
},
content_type:
"application/vnd.git-lfs+json"
,
status:
403
)
end
def
render_lfs_not_found
render
(
json:
{
message:
'Not found.'
,
documentation_url:
"
#{
Gitlab
.
config
.
gitlab
.
url
}
/help"
,
},
content_type:
"application/vnd.git-lfs+json"
,
status:
404
)
end
def
storage_project
@storage_project
||=
begin
result
=
project
while
result
.
forked?
do
result
=
result
.
forked_from_project
end
result
end
end
end
config/routes.rb
View file @
0d975244
...
@@ -85,9 +85,6 @@ Rails.application.routes.draw do
...
@@ -85,9 +85,6 @@ Rails.application.routes.draw do
# Health check
# Health check
get
'health_check(/:checks)'
=>
'health_check#index'
,
as: :health_check
get
'health_check(/:checks)'
=>
'health_check#index'
,
as: :health_check
# Enable Grack support (for LFS only)
mount
Grack
::
AuthSpawner
,
at:
'/'
,
constraints:
lambda
{
|
request
|
/[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/
.
match
(
request
.
path_info
)
},
via:
[
:get
,
:post
,
:put
]
# Help
# Help
get
'help'
=>
'help#index'
get
'help'
=>
'help#index'
get
'help/shortcuts'
=>
'help#shortcuts'
get
'help/shortcuts'
=>
'help#shortcuts'
...
@@ -483,11 +480,26 @@ Rails.application.routes.draw do
...
@@ -483,11 +480,26 @@ Rails.application.routes.draw do
end
end
scope
module: :projects
do
scope
module: :projects
do
# Git HTTP clients ('git clone' etc.)
scope
constraints:
{
id:
/.+\.git/
,
format:
nil
}
do
scope
constraints:
{
id:
/.+\.git/
,
format:
nil
}
do
# Git HTTP clients ('git clone' etc.)
get
'/info/refs'
,
to:
'git_http#info_refs'
get
'/info/refs'
,
to:
'git_http#info_refs'
post
'/git-upload-pack'
,
to:
'git_http#git_upload_pack'
post
'/git-upload-pack'
,
to:
'git_http#git_upload_pack'
post
'/git-receive-pack'
,
to:
'git_http#git_receive_pack'
post
'/git-receive-pack'
,
to:
'git_http#git_receive_pack'
# Git LFS API (metadata)
post
'/info/lfs/objects/batch'
,
to:
'lfs_api#batch'
post
'/info/lfs/objects'
,
to:
'lfs_api#deprecated'
get
'/info/lfs/objects/*oid'
,
to:
'lfs_api#deprecated'
# GitLab LFS object storage
scope
constraints:
{
oid:
/[a-f0-9]{64}/
}
do
get
'/gitlab-lfs/objects/*oid'
,
to:
'lfs_storage#download'
scope
constraints:
{
size:
/[0-9]+/
}
do
put
'/gitlab-lfs/objects/*oid/*size/authorize'
,
to:
'lfs_storage#upload_authorize'
put
'/gitlab-lfs/objects/*oid/*size'
,
to:
'lfs_storage#upload_finalize'
end
end
end
end
# Allow /info/refs, /info/refs?service=git-upload-pack, and
# Allow /info/refs, /info/refs?service=git-upload-pack, and
...
...
spec/requests/lfs_http_spec.rb
View file @
0d975244
...
@@ -31,6 +31,7 @@ describe Gitlab::Lfs::Router do
...
@@ -31,6 +31,7 @@ describe Gitlab::Lfs::Router do
'operation'
=>
'upload'
'operation'
=>
'upload'
}
}
end
end
let
(
:authorization
)
{
authorize_user
}
before
do
before
do
allow
(
Gitlab
.
config
.
lfs
).
to
receive
(
:enabled
).
and_return
(
false
)
allow
(
Gitlab
.
config
.
lfs
).
to
receive
(
:enabled
).
and_return
(
false
)
...
@@ -71,6 +72,7 @@ describe Gitlab::Lfs::Router do
...
@@ -71,6 +72,7 @@ describe Gitlab::Lfs::Router do
end
end
context
'when handling lfs request using deprecated API'
do
context
'when handling lfs request using deprecated API'
do
let
(
:authorization
)
{
authorize_user
}
before
do
before
do
post_json
"
#{
project
.
http_url_to_repo
}
/info/lfs/objects"
,
nil
,
headers
post_json
"
#{
project
.
http_url_to_repo
}
/info/lfs/objects"
,
nil
,
headers
end
end
...
@@ -118,8 +120,8 @@ describe Gitlab::Lfs::Router do
...
@@ -118,8 +120,8 @@ describe Gitlab::Lfs::Router do
project
.
lfs_objects
<<
lfs_object
project
.
lfs_objects
<<
lfs_object
end
end
it
'responds with status 40
3
'
do
it
'responds with status 40
4
'
do
expect
(
response
).
to
have_http_status
(
40
3
)
expect
(
response
).
to
have_http_status
(
40
4
)
end
end
end
end
...
@@ -147,8 +149,8 @@ describe Gitlab::Lfs::Router do
...
@@ -147,8 +149,8 @@ describe Gitlab::Lfs::Router do
context
'without required headers'
do
context
'without required headers'
do
let
(
:authorization
)
{
authorize_user
}
let
(
:authorization
)
{
authorize_user
}
it
'responds with status 40
3
'
do
it
'responds with status 40
4
'
do
expect
(
response
).
to
have_http_status
(
40
3
)
expect
(
response
).
to
have_http_status
(
40
4
)
end
end
end
end
end
end
...
@@ -304,10 +306,10 @@ describe Gitlab::Lfs::Router do
...
@@ -304,10 +306,10 @@ describe Gitlab::Lfs::Router do
end
end
context
'when user does is not member of the project'
do
context
'when user does is not member of the project'
do
let
(
:
role
)
{
:guest
}
let
(
:
update_user_permissions
)
{
nil
}
it
'responds with 40
3
'
do
it
'responds with 40
4
'
do
expect
(
response
).
to
have_http_status
(
40
3
)
expect
(
response
).
to
have_http_status
(
40
4
)
end
end
end
end
...
@@ -510,6 +512,7 @@ describe Gitlab::Lfs::Router do
...
@@ -510,6 +512,7 @@ describe Gitlab::Lfs::Router do
describe
'unsupported'
do
describe
'unsupported'
do
let
(
:project
)
{
create
(
:empty_project
)
}
let
(
:project
)
{
create
(
:empty_project
)
}
let
(
:authorization
)
{
authorize_user
}
let
(
:body
)
do
let
(
:body
)
do
{
'operation'
=>
'other'
,
{
'operation'
=>
'other'
,
'objects'
=>
[
'objects'
=>
[
...
@@ -557,7 +560,7 @@ describe Gitlab::Lfs::Router do
...
@@ -557,7 +560,7 @@ describe Gitlab::Lfs::Router do
end
end
it
'does not recognize it as a valid lfs command'
do
it
'does not recognize it as a valid lfs command'
do
expect
(
response
).
to
have_http_status
(
40
3
)
expect
(
response
).
to
have_http_status
(
40
1
)
end
end
end
end
end
end
...
@@ -582,6 +585,16 @@ describe Gitlab::Lfs::Router do
...
@@ -582,6 +585,16 @@ describe Gitlab::Lfs::Router do
expect
(
response
).
to
have_http_status
(
403
)
expect
(
response
).
to
have_http_status
(
403
)
end
end
end
end
context
'and request is sent with a malformed headers'
do
before
do
put_finalize
(
'cat /etc/passwd'
)
end
it
'does not recognize it as a valid lfs command'
do
expect
(
response
).
to
have_http_status
(
403
)
end
end
end
end
describe
'to one project'
do
describe
'to one project'
do
...
@@ -627,6 +640,10 @@ describe Gitlab::Lfs::Router do
...
@@ -627,6 +640,10 @@ describe Gitlab::Lfs::Router do
end
end
describe
'and user does not have push access'
do
describe
'and user does not have push access'
do
before
do
project
.
team
<<
[
user
,
:reporter
]
end
it_behaves_like
'forbidden'
it_behaves_like
'forbidden'
end
end
end
end
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment