Commit 42ada09c authored by Stan Hu's avatar Stan Hu

Merge branch 'fj-28429-generate-lfs-token-authorization' into 'master'

Generate LFS token authorization for user LFS requests

Closes #32553 and #28429

See merge request gitlab-org/gitlab!17332
parents d1bfec9b b2bfd36b
...@@ -288,9 +288,7 @@ class ApplicationController < ActionController::Base ...@@ -288,9 +288,7 @@ class ApplicationController < ActionController::Base
def check_password_expiration def check_password_expiration
return if session[:impersonator_id] || !current_user&.allow_password_authentication? return if session[:impersonator_id] || !current_user&.allow_password_authentication?
password_expires_at = current_user&.password_expires_at if current_user&.password_expired?
if password_expires_at && password_expires_at < Time.now
return redirect_to new_profile_password_path return redirect_to new_profile_password_path
end end
end end
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class Projects::LfsApiController < Projects::GitHttpClientController class Projects::LfsApiController < Projects::GitHttpClientController
include LfsRequest include LfsRequest
include Gitlab::Utils::StrongMemoize
LFS_TRANSFER_CONTENT_TYPE = 'application/octet-stream' LFS_TRANSFER_CONTENT_TYPE = 'application/octet-stream'
...@@ -81,7 +82,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -81,7 +82,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
download: { download: {
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}", href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}",
header: { header: {
Authorization: request.headers['Authorization'] Authorization: authorization_header
}.compact }.compact
} }
} }
...@@ -92,7 +93,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -92,7 +93,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
upload: { upload: {
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}/#{object[:size]}", href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}/#{object[:size]}",
header: { header: {
Authorization: request.headers['Authorization'], Authorization: authorization_header,
# git-lfs v2.5.0 sets the Content-Type based on the uploaded file. This # git-lfs v2.5.0 sets the Content-Type based on the uploaded file. This
# ensures that Workhorse can intercept the request. # ensures that Workhorse can intercept the request.
'Content-Type': LFS_TRANSFER_CONTENT_TYPE 'Content-Type': LFS_TRANSFER_CONTENT_TYPE
...@@ -122,6 +123,18 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -122,6 +123,18 @@ class Projects::LfsApiController < Projects::GitHttpClientController
def lfs_read_only_message def lfs_read_only_message
_('You cannot write to this read-only GitLab instance.') _('You cannot write to this read-only GitLab instance.')
end end
def authorization_header
strong_memoize(:authorization_header) do
lfs_auth_header || request.headers['Authorization']
end
end
def lfs_auth_header
return unless user.is_a?(User)
Gitlab::LfsToken.new(user).basic_encoding
end
end end
Projects::LfsApiController.prepend_if_ee('EE::Projects::LfsApiController') Projects::LfsApiController.prepend_if_ee('EE::Projects::LfsApiController')
...@@ -1519,6 +1519,10 @@ class User < ApplicationRecord ...@@ -1519,6 +1519,10 @@ class User < ApplicationRecord
todos.find_by(target: target, state: :pending) todos.find_by(target: target, state: :pending)
end end
def password_expired?
!!(password_expires_at && password_expires_at < Time.now)
end
# @deprecated # @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
......
---
title: Generate LFS token authorization for user LFS requests
merge_request: 17332
author:
type: fixed
...@@ -231,7 +231,7 @@ module Gitlab ...@@ -231,7 +231,7 @@ module Gitlab
authentication_abilities = authentication_abilities =
if token_handler.user? if token_handler.user?
full_authentication_abilities read_write_project_authentication_abilities
elsif token_handler.deploy_key_pushable?(project) elsif token_handler.deploy_key_pushable?(project)
read_write_authentication_abilities read_write_authentication_abilities
else else
...@@ -272,10 +272,21 @@ module Gitlab ...@@ -272,10 +272,21 @@ module Gitlab
] ]
end end
def read_only_authentication_abilities def read_only_project_authentication_abilities
[ [
:read_project, :read_project,
:download_code, :download_code
]
end
def read_write_project_authentication_abilities
read_only_project_authentication_abilities + [
:push_code
]
end
def read_only_authentication_abilities
read_only_project_authentication_abilities + [
:read_container_image :read_container_image
] ]
end end
......
...@@ -34,8 +34,11 @@ module Gitlab ...@@ -34,8 +34,11 @@ module Gitlab
HMACToken.new(actor).token(DEFAULT_EXPIRE_TIME) HMACToken.new(actor).token(DEFAULT_EXPIRE_TIME)
end end
# When the token is an lfs one and the actor
# is blocked or the password has been changed,
# the token is no longer valid
def token_valid?(token_to_check) def token_valid?(token_to_check)
HMACToken.new(actor).token_valid?(token_to_check) HMACToken.new(actor).token_valid?(token_to_check) && valid_user?
end end
def deploy_key_pushable?(project) def deploy_key_pushable?(project)
...@@ -46,6 +49,12 @@ module Gitlab ...@@ -46,6 +49,12 @@ module Gitlab
user? ? :lfs_token : :lfs_deploy_token user? ? :lfs_token : :lfs_deploy_token
end end
def valid_user?
return true unless user?
!actor.blocked? && (!actor.allow_password_authentication? || !actor.password_expired?)
end
def authentication_payload(repository_http_path) def authentication_payload(repository_http_path)
{ {
username: actor_name, username: actor_name,
...@@ -55,6 +64,10 @@ module Gitlab ...@@ -55,6 +64,10 @@ module Gitlab
} }
end end
def basic_encoding
ActionController::HttpAuthentication::Basic.encode_credentials(actor_name, token)
end
private # rubocop:disable Lint/UselessAccessModifier private # rubocop:disable Lint/UselessAccessModifier
class HMACToken class HMACToken
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Auth do describe Gitlab::Auth do
let(:gl_auth) { described_class } let(:gl_auth) { described_class }
set(:project) { create(:project) }
describe 'constants' do describe 'constants' do
it 'API_SCOPES contains all scopes for API access' do it 'API_SCOPES contains all scopes for API access' do
...@@ -90,13 +91,13 @@ describe Gitlab::Auth do ...@@ -90,13 +91,13 @@ describe Gitlab::Auth do
end end
it 'recognises user-less build' do it 'recognises user-less build' do
expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities)) expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, described_class.build_authentication_abilities))
end end
it 'recognises user token' do it 'recognises user token' do
build.update(user: create(:user)) build.update(user: create(:user))
expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)) expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, described_class.build_authentication_abilities))
end end
end end
...@@ -117,26 +118,25 @@ describe Gitlab::Auth do ...@@ -117,26 +118,25 @@ describe Gitlab::Auth do
end end
it 'recognizes other ci services' do it 'recognizes other ci services' do
project = create(:project)
project.create_drone_ci_service(active: true) project.create_drone_ci_service(active: true)
project.drone_ci_service.update(token: 'token') project.drone_ci_service.update(token: 'token')
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'drone-ci-token') expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'drone-ci-token')
expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities)) expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, project, :ci, described_class.build_authentication_abilities))
end end
it 'recognizes master passwords' do it 'recognizes master passwords' do
user = create(:user, password: 'password') user = create(:user, password: 'password')
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username) expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities))
end end
include_examples 'user login operation with unique ip limit' do include_examples 'user login operation with unique ip limit' do
let(:user) { create(:user, password: 'password') } let(:user) { create(:user, password: 'password') }
def operation def operation
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities))
end end
end end
...@@ -146,7 +146,7 @@ describe Gitlab::Auth do ...@@ -146,7 +146,7 @@ describe Gitlab::Auth do
token = Gitlab::LfsToken.new(user).token token = Gitlab::LfsToken.new(user).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username) expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities)) expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, described_class.read_write_project_authentication_abilities))
end end
it 'recognizes deploy key lfs tokens' do it 'recognizes deploy key lfs tokens' do
...@@ -154,7 +154,7 @@ describe Gitlab::Auth do ...@@ -154,7 +154,7 @@ describe Gitlab::Auth do
token = Gitlab::LfsToken.new(key).token token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_only_authentication_abilities)) expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, described_class.read_only_authentication_abilities))
end end
it 'does not try password auth before oauth' do it 'does not try password auth before oauth' do
...@@ -167,22 +167,20 @@ describe Gitlab::Auth do ...@@ -167,22 +167,20 @@ describe Gitlab::Auth do
end end
it 'grants deploy key write permissions' do it 'grants deploy key write permissions' do
project = create(:project)
key = create(:deploy_key) key = create(:deploy_key)
create(:deploy_keys_project, :write_access, deploy_key: key, project: project) create(:deploy_keys_project, :write_access, deploy_key: key, project: project)
token = Gitlab::LfsToken.new(key).token token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_write_authentication_abilities)) expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, described_class.read_write_authentication_abilities))
end end
it 'does not grant deploy key write permissions' do it 'does not grant deploy key write permissions' do
project = create(:project)
key = create(:deploy_key) key = create(:deploy_key)
token = Gitlab::LfsToken.new(key).token token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_only_authentication_abilities)) expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, described_class.read_only_authentication_abilities))
end end
end end
...@@ -193,7 +191,7 @@ describe Gitlab::Auth do ...@@ -193,7 +191,7 @@ describe Gitlab::Auth do
it 'succeeds for OAuth tokens with the `api` scope' do it 'succeeds for OAuth tokens with the `api` scope' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2') expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2')
expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)) expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, described_class.full_authentication_abilities))
end end
it 'fails for OAuth tokens with other scopes' do it 'fails for OAuth tokens with other scopes' do
...@@ -214,7 +212,7 @@ describe Gitlab::Auth do ...@@ -214,7 +212,7 @@ describe Gitlab::Auth do
it 'succeeds for personal access tokens with the `api` scope' do it 'succeeds for personal access tokens with the `api` scope' do
personal_access_token = create(:personal_access_token, scopes: ['api']) personal_access_token = create(:personal_access_token, scopes: ['api'])
expect_results_with_abilities(personal_access_token, full_authentication_abilities) expect_results_with_abilities(personal_access_token, described_class.full_authentication_abilities)
end end
it 'succeeds for personal access tokens with the `read_repository` scope' do it 'succeeds for personal access tokens with the `read_repository` scope' do
...@@ -244,7 +242,7 @@ describe Gitlab::Auth do ...@@ -244,7 +242,7 @@ describe Gitlab::Auth do
it 'succeeds if it is an impersonation token' do it 'succeeds if it is an impersonation token' do
impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api']) impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
expect_results_with_abilities(impersonation_token, full_authentication_abilities) expect_results_with_abilities(impersonation_token, described_class.full_authentication_abilities)
end end
it 'limits abilities based on scope' do it 'limits abilities based on scope' do
...@@ -267,7 +265,7 @@ describe Gitlab::Auth do ...@@ -267,7 +265,7 @@ describe Gitlab::Auth do
) )
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities))
end end
it 'fails through oauth authentication when the username is oauth2' do it 'fails through oauth authentication when the username is oauth2' do
...@@ -278,7 +276,7 @@ describe Gitlab::Auth do ...@@ -278,7 +276,7 @@ describe Gitlab::Auth do
) )
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities))
end end
end end
...@@ -296,7 +294,6 @@ describe Gitlab::Auth do ...@@ -296,7 +294,6 @@ describe Gitlab::Auth do
end end
context 'while using deploy tokens' do context 'while using deploy tokens' do
let(:project) { create(:project) }
let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) } let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) }
context 'when deploy token and user have the same username' do context 'when deploy token and user have the same username' do
...@@ -316,7 +313,7 @@ describe Gitlab::Auth do ...@@ -316,7 +313,7 @@ describe Gitlab::Auth do
end end
it 'succeeds for the user' do it 'succeeds for the user' do
auth_success = Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities) auth_success = Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities)
expect(gl_auth.find_for_git_client(username, 'my-secret', project: project, ip: 'ip')) expect(gl_auth.find_for_git_client(username, 'my-secret', project: project, ip: 'ip'))
.to eq(auth_success) .to eq(auth_success)
...@@ -344,7 +341,7 @@ describe Gitlab::Auth do ...@@ -344,7 +341,7 @@ describe Gitlab::Auth do
end end
context 'and belong to different projects' do context 'and belong to different projects' do
let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [create(:project)]) } let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) }
let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) } let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) }
it 'succeeds for the right token' do it 'succeeds for the right token' do
...@@ -582,37 +579,6 @@ describe Gitlab::Auth do ...@@ -582,37 +579,6 @@ describe Gitlab::Auth do
private private
def build_authentication_abilities
[
:read_project,
:build_download_code,
:build_read_container_image,
:build_create_container_image,
:build_destroy_container_image
]
end
def read_only_authentication_abilities
[
:read_project,
:download_code,
:read_container_image
]
end
def read_write_authentication_abilities
read_only_authentication_abilities + [
:push_code,
:create_container_image
]
end
def full_authentication_abilities
read_write_authentication_abilities + [
:admin_container_image
]
end
def expect_results_with_abilities(personal_access_token, abilities, success = true) def expect_results_with_abilities(personal_access_token, abilities, success = true)
expect(gl_auth).to receive(:rate_limit!).with('ip', success: success, login: '') expect(gl_auth).to receive(:rate_limit!).with('ip', success: success, login: '')
expect(gl_auth.find_for_git_client('', personal_access_token&.token, project: nil, ip: 'ip')) expect(gl_auth.find_for_git_client('', personal_access_token&.token, project: nil, ip: 'ip'))
......
...@@ -115,6 +115,46 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do ...@@ -115,6 +115,46 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy
end end
end end
context 'when the actor is a regular user' do
context 'when the user is blocked' do
let(:actor) { create(:user, :blocked) }
it 'returns false' do
expect(lfs_token.token_valid?(lfs_token.token)).to be_falsey
end
end
context 'when the user password is expired' do
let(:actor) { create(:user, password_expires_at: 1.minute.ago) }
it 'returns false' do
expect(lfs_token.token_valid?(lfs_token.token)).to be_falsey
end
end
end
context 'when the actor is an ldap user' do
before do
allow(actor).to receive(:ldap_user?).and_return(true)
end
context 'when the user is blocked' do
let(:actor) { create(:user, :blocked) }
it 'returns false' do
expect(lfs_token.token_valid?(lfs_token.token)).to be_falsey
end
end
context 'when the user password is expired' do
let(:actor) { create(:user, password_expires_at: 1.minute.ago) }
it 'returns true' do
expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy
end
end
end
end end
end end
......
...@@ -3616,4 +3616,34 @@ describe User do ...@@ -3616,4 +3616,34 @@ describe User do
end end
end end
end end
describe '#password_expired?' do
let(:user) { build(:user, password_expires_at: password_expires_at) }
subject { user.password_expired? }
context 'when password_expires_at is not set' do
let(:password_expires_at) {}
it 'returns false' do
is_expected.to be_falsey
end
end
context 'when password_expires_at is in the past' do
let(:password_expires_at) { 1.minute.ago }
it 'returns true' do
is_expected.to be_truthy
end
end
context 'when password_expires_at is in the future' do
let(:password_expires_at) { 1.minute.from_now }
it 'returns false' do
is_expected.to be_falsey
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe 'Git LFS API and storage' do describe 'Git LFS API and storage' do
include WorkhorseHelpers include LfsHttpHelpers
include ProjectForksHelper include ProjectForksHelper
let(:user) { create(:user) } set(:project) { create(:project, :repository) }
set(:other_project) { create(:project, :repository) }
set(:user) { create(:user) }
let!(:lfs_object) { create(:lfs_object, :with_file) } let!(:lfs_object) { create(:lfs_object, :with_file) }
let(:headers) do let(:headers) do
...@@ -19,201 +22,163 @@ describe 'Git LFS API and storage' do ...@@ -19,201 +22,163 @@ describe 'Git LFS API and storage' do
let(:sample_oid) { lfs_object.oid } let(:sample_oid) { lfs_object.oid }
let(:sample_size) { lfs_object.size } let(:sample_size) { lfs_object.size }
let(:sample_object) { { 'oid' => sample_oid, 'size' => sample_size } }
let(:non_existing_object_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
let(:non_existing_object_size) { 1575078 }
let(:non_existing_object) { { 'oid' => non_existing_object_oid, 'size' => non_existing_object_size } }
let(:multiple_objects) { [sample_object, non_existing_object] }
describe 'when lfs is disabled' do let(:lfs_enabled) { true }
let(:project) { create(:project) }
let(:body) do before do
{ stub_lfs_setting(enabled: lfs_enabled)
'objects' => [ end
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 }, describe 'when LFS is disabled' do
{ 'oid' => sample_oid, let(:lfs_enabled) { false }
'size' => sample_size } let(:body) { upload_body(multiple_objects) }
],
'operation' => 'upload'
}
end
let(:authorization) { authorize_user } let(:authorization) { authorize_user }
before do before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) post_lfs_json batch_url(project), body, headers
post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
end end
it 'responds with 501' do it_behaves_like 'LFS http 501 response'
expect(response).to have_gitlab_http_status(501)
expect(json_response).to include('message' => 'Git LFS is not enabled on this GitLab server, contact your admin.')
end
end end
context 'project specific LFS settings' do context 'project specific LFS settings' do
let(:project) { create(:project) } let(:body) { upload_body(sample_object) }
let(:body) do
{
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 },
{ 'oid' => sample_oid,
'size' => sample_size }
],
'operation' => 'upload'
}
end
let(:authorization) { authorize_user } let(:authorization) { authorize_user }
before do
project.add_maintainer(user)
project.update_attribute(:lfs_enabled, project_lfs_enabled)
subject
end
context 'with LFS disabled globally' do context 'with LFS disabled globally' do
before do let(:lfs_enabled) { false }
project.add_maintainer(user)
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
end
describe 'LFS disabled in project' do describe 'LFS disabled in project' do
before do let(:project_lfs_enabled) { false }
project.update_attribute(:lfs_enabled, false)
end
it 'responds with a 501 message on upload' do context 'when uploading' do
post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers subject { post_lfs_json(batch_url(project), body, headers) }
expect(response).to have_gitlab_http_status(501) it_behaves_like 'LFS http 501 response'
end end
it 'responds with a 501 message on download' do context 'when downloading' do
get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
expect(response).to have_gitlab_http_status(501) it_behaves_like 'LFS http 501 response'
end end
end end
describe 'LFS enabled in project' do describe 'LFS enabled in project' do
before do let(:project_lfs_enabled) { true }
project.update_attribute(:lfs_enabled, true)
end
it 'responds with a 501 message on upload' do context 'when uploading' do
post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers subject { post_lfs_json(batch_url(project), body, headers) }
expect(response).to have_gitlab_http_status(501) it_behaves_like 'LFS http 501 response'
end end
it 'responds with a 501 message on download' do context 'when downloading' do
get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
expect(response).to have_gitlab_http_status(501) it_behaves_like 'LFS http 501 response'
end end
end end
end end
context 'with LFS enabled globally' do context 'with LFS enabled globally' do
before do
project.add_maintainer(user)
enable_lfs
end
describe 'LFS disabled in project' do describe 'LFS disabled in project' do
before do let(:project_lfs_enabled) { false }
project.update_attribute(:lfs_enabled, false)
end
it 'responds with a 403 message on upload' do context 'when uploading' do
post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers subject { post_lfs_json(batch_url(project), body, headers) }
expect(response).to have_gitlab_http_status(403) it_behaves_like 'LFS http 403 response'
expect(json_response).to include('message' => 'Access forbidden. Check your access level.')
end end
it 'responds with a 403 message on download' do context 'when downloading' do
get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
expect(response).to have_gitlab_http_status(403) it_behaves_like 'LFS http 403 response'
expect(json_response).to include('message' => 'Access forbidden. Check your access level.')
end end
end end
describe 'LFS enabled in project' do describe 'LFS enabled in project' do
before do let(:project_lfs_enabled) { true }
project.update_attribute(:lfs_enabled, true)
end
it 'responds with a 200 message on upload' do context 'when uploading' do
post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers subject { post_lfs_json(batch_url(project), body, headers) }
expect(response).to have_gitlab_http_status(200) it_behaves_like 'LFS http 200 response'
expect(json_response['objects'].first['size']).to eq(1575078)
end end
it 'responds with a 200 message on download' do context 'when downloading' do
get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
expect(response).to have_gitlab_http_status(200) it_behaves_like 'LFS http 200 response'
end end
end end
end end
end end
describe 'deprecated API' do describe 'deprecated API' do
let(:project) { create(:project) } let(:authorization) { authorize_user }
before do
enable_lfs
end
shared_examples 'a deprecated' do shared_examples 'deprecated request' do
it 'responds with 501' do before do
expect(response).to have_gitlab_http_status(501) subject
end end
it 'returns deprecated message' do it_behaves_like 'LFS http expected response code and message' do
expect(json_response).to include('message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.') let(:response_code) { 501 }
let(:message) { 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.' }
end end
end end
context 'when fetching lfs object using deprecated API' do context 'when fetching LFS object using deprecated API' do
let(:authorization) { authorize_user } subject { get(deprecated_objects_url(project, sample_oid), params: {}, headers: headers) }
before do
get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", params: {}, headers: headers
end
it_behaves_like 'a deprecated' it_behaves_like 'deprecated request'
end end
context 'when handling lfs request using deprecated API' do context 'when handling LFS request using deprecated API' do
let(:authorization) { authorize_user } subject { post_lfs_json(deprecated_objects_url(project), nil, headers) }
before do
post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects", nil, headers
end
it_behaves_like 'a deprecated' it_behaves_like 'deprecated request'
end
def deprecated_objects_url(project, oid = nil)
File.join(["#{project.http_url_to_repo}/info/lfs/objects/", oid].compact)
end end
end end
describe 'when fetching lfs object' do describe 'when fetching LFS object' do
let(:project) { create(:project) }
let(:update_permissions) { } let(:update_permissions) { }
let(:before_get) { } let(:before_get) { }
before do before do
enable_lfs
update_permissions update_permissions
before_get before_get
get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers get objects_url(project, sample_oid), params: {}, headers: headers
end end
context 'and request comes from gitlab-workhorse' do context 'and request comes from gitlab-workhorse' do
context 'without user being authorized' do context 'without user being authorized' do
it 'responds with status 401' do it_behaves_like 'LFS http 401 response'
expect(response).to have_gitlab_http_status(401)
end
end end
context 'with required headers' do context 'with required headers' do
shared_examples 'responds with a file' do shared_examples 'responds with a file' do
let(:sendfile) { 'X-Sendfile' } let(:sendfile) { 'X-Sendfile' }
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'responds with the file location' do it 'responds with the file location' do
expect(response.headers['Content-Type']).to eq('application/octet-stream') expect(response.headers['Content-Type']).to eq('application/octet-stream')
...@@ -229,9 +194,7 @@ describe 'Git LFS API and storage' do ...@@ -229,9 +194,7 @@ describe 'Git LFS API and storage' do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with status 404' do it_behaves_like 'LFS http 404 response'
expect(response).to have_gitlab_http_status(404)
end
end end
context 'and does have project access' do context 'and does have project access' do
...@@ -249,9 +212,7 @@ describe 'Git LFS API and storage' do ...@@ -249,9 +212,7 @@ describe 'Git LFS API and storage' do
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE) lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
end end
it 'responds with redirect' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'responds with the workhorse send-url' do it 'responds with the workhorse send-url' do
expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:") expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
...@@ -288,7 +249,7 @@ describe 'Git LFS API and storage' do ...@@ -288,7 +249,7 @@ describe 'Git LFS API and storage' do
it_behaves_like 'responds with a file' it_behaves_like 'responds with a file'
end end
describe 'when using a user key' do describe 'when using a user key (LFSToken)' do
let(:authorization) { authorize_user_key } let(:authorization) { authorize_user_key }
context 'when user allowed' do context 'when user allowed' do
...@@ -298,6 +259,18 @@ describe 'Git LFS API and storage' do ...@@ -298,6 +259,18 @@ describe 'Git LFS API and storage' do
end end
it_behaves_like 'responds with a file' it_behaves_like 'responds with a file'
context 'when user password is expired' do
let(:user) { create(:user, password_expires_at: 1.minute.ago)}
it_behaves_like 'LFS http 401 response'
end
context 'when user is blocked' do
let(:user) { create(:user, :blocked)}
it_behaves_like 'LFS http 401 response'
end
end end
context 'when user not allowed' do context 'when user not allowed' do
...@@ -305,9 +278,7 @@ describe 'Git LFS API and storage' do ...@@ -305,9 +278,7 @@ describe 'Git LFS API and storage' do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with status 404' do it_behaves_like 'LFS http 404 response'
expect(response).to have_gitlab_http_status(404)
end
end end
end end
...@@ -337,7 +308,6 @@ describe 'Git LFS API and storage' do ...@@ -337,7 +308,6 @@ describe 'Git LFS API and storage' do
end end
context 'for other project' do context 'for other project' do
let(:other_project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:update_permissions) do let(:update_permissions) do
...@@ -361,7 +331,6 @@ describe 'Git LFS API and storage' do ...@@ -361,7 +331,6 @@ describe 'Git LFS API and storage' do
end end
context 'regular user' do context 'regular user' do
let(:user) { create(:user) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do it_behaves_like 'can download LFS only from own projects' do
...@@ -384,166 +353,147 @@ describe 'Git LFS API and storage' do ...@@ -384,166 +353,147 @@ describe 'Git LFS API and storage' do
context 'without required headers' do context 'without required headers' do
let(:authorization) { authorize_user } let(:authorization) { authorize_user }
it 'responds with status 404' do it_behaves_like 'LFS http 404 response'
expect(response).to have_gitlab_http_status(404)
end
end end
end end
end end
describe 'when handling lfs batch request' do describe 'when handling LFS batch request' do
let(:update_lfs_permissions) { } let(:update_lfs_permissions) { }
let(:update_user_permissions) { } let(:update_user_permissions) { }
before do before do
enable_lfs
update_lfs_permissions update_lfs_permissions
update_user_permissions update_user_permissions
post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers post_lfs_json batch_url(project), body, headers
end end
describe 'download' do shared_examples 'process authorization header' do |renew_authorization:|
let(:project) { create(:project) } let(:response_authorization) do
let(:body) do authorization_in_action(lfs_actions.first)
{
'operation' => 'download',
'objects' => [
{ 'oid' => sample_oid,
'size' => sample_size }
]
}
end end
shared_examples 'an authorized requests' do if renew_authorization
context 'when downloading an lfs object that is assigned to our project' do context 'when the authorization comes from a user' do
let(:update_lfs_permissions) do it 'returns a new valid LFS token authorization' do
project.lfs_objects << lfs_object expect(response_authorization).not_to eq(authorization)
end end
it 'responds with status 200' do it 'returns a a valid token' do
expect(response).to have_gitlab_http_status(200) username, token = ::Base64.decode64(response_authorization.split(' ', 2).last).split(':', 2)
expect(username).to eq(user.username)
expect(Gitlab::LfsToken.new(user).token_valid?(token)).to be_truthy
end end
it 'with href to download' do it 'generates only one new token per each request' do
expect(json_response).to eq({ authorizations = lfs_actions.map do |action|
'objects' => [ authorization_in_action(action)
{ end.compact
'oid' => sample_oid,
'size' => sample_size, expect(authorizations.uniq.count).to eq 1
'actions' => { end
'download' => { end
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", else
'header' => { 'Authorization' => authorization } context 'when the authorization comes from a token' do
} it 'returns the same authorization header' do
} expect(response_authorization).to eq(authorization)
}
]
})
end end
end end
end
def lfs_actions
json_response['objects'].map { |a| a['actions'] }.compact
end
context 'when downloading an lfs object that is assigned to other project' do def authorization_in_action(action)
let(:other_project) { create(:project) } (action['upload'] || action['download']).dig('header', 'Authorization')
end
end
describe 'download' do
let(:body) { download_body(sample_object) }
shared_examples 'an authorized request' do |renew_authorization:|
context 'when downloading an LFS object that is assigned to our project' do
let(:update_lfs_permissions) do let(:update_lfs_permissions) do
other_project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'with href to download' do it 'with href to download' do
expect(json_response).to eq({ expect(json_response['objects'].first).to include(sample_object)
'objects' => [ expect(json_response['objects'].first['actions']['download']['href']).to eq(objects_url(project, sample_oid))
{
'oid' => sample_oid,
'size' => sample_size,
'error' => {
'code' => 404,
'message' => "Object does not exist on the server or you don't have permissions to access it"
}
}
]
})
end end
it_behaves_like 'process authorization header', renew_authorization: renew_authorization
end end
context 'when downloading a lfs object that does not exist' do context 'when downloading an LFS object that is assigned to other project' do
let(:body) do let(:update_lfs_permissions) do
{ other_project.lfs_objects << lfs_object
'operation' => 'download',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 }
]
}
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'with an 404 for specific object' do it 'with an 404 for specific object' do
expect(json_response).to eq({ expect(json_response['objects'].first).to include(sample_object)
'objects' => [ expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it")
{
'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078,
'error' => {
'code' => 404,
'message' => "Object does not exist on the server or you don't have permissions to access it"
}
}
]
})
end end
end end
context 'when downloading one new and one existing lfs object' do context 'when downloading a LFS object that does not exist' do
let(:body) do let(:body) { download_body(non_existing_object) }
{
'operation' => 'download', it_behaves_like 'LFS http 200 response'
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', it 'with an 404 for specific object' do
'size' => 1575078 }, expect(json_response['objects'].first).to include(non_existing_object)
{ 'oid' => sample_oid, expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it")
'size' => sample_size }
]
}
end end
end
context 'when downloading one new and one existing LFS object' do
let(:body) { download_body(multiple_objects) }
let(:update_lfs_permissions) do let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'responds with upload hypermedia link for the new object' do it 'responds with download hypermedia link for the new object' do
expect(json_response).to eq({ expect(json_response['objects'].first).to include(sample_object)
'objects' => [ expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid))
{ expect(json_response['objects'].last).to eq({
'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', 'oid' => non_existing_object_oid,
'size' => 1575078, 'size' => non_existing_object_size,
'error' => { 'error' => {
'code' => 404, 'code' => 404,
'message' => "Object does not exist on the server or you don't have permissions to access it" 'message' => "Object does not exist on the server or you don't have permissions to access it"
} }
},
{
'oid' => sample_oid,
'size' => sample_size,
'actions' => {
'download' => {
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => { 'Authorization' => authorization }
}
}
}
]
}) })
end end
it_behaves_like 'process authorization header', renew_authorization: renew_authorization
end
context 'when downloading two existing LFS objects' do
let(:body) { download_body(multiple_objects) }
let(:other_object) { create(:lfs_object, :with_file, oid: non_existing_object_oid, size: non_existing_object_size) }
let(:update_lfs_permissions) do
project.lfs_objects << [lfs_object, other_object]
end
it 'responds with the download hypermedia link for each object' do
expect(json_response['objects'].first).to include(sample_object)
expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid))
expect(json_response['objects'].last).to include(non_existing_object)
expect(json_response['objects'].last['actions']['download']).to include('href' => objects_url(project, non_existing_object_oid))
end
it_behaves_like 'process authorization header', renew_authorization: renew_authorization
end end
end end
...@@ -554,29 +504,41 @@ describe 'Git LFS API and storage' do ...@@ -554,29 +504,41 @@ describe 'Git LFS API and storage' do
project.add_role(user, role) project.add_role(user, role)
end end
it_behaves_like 'an authorized requests' do it_behaves_like 'an authorized request', renew_authorization: true do
let(:role) { :reporter } let(:role) { :reporter }
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(:update_user_permissions) { nil } let(:update_user_permissions) { nil }
it 'responds with 404' do it_behaves_like 'LFS http 404 response'
expect(response).to have_gitlab_http_status(404)
end
end end
context 'when user does not have download access' do context 'when user does not have download access' do
let(:role) { :guest } let(:role) { :guest }
it 'responds with 403' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403) end
context 'when user password is expired' do
let(:role) { :reporter}
let(:user) { create(:user, password_expires_at: 1.minute.ago)}
it 'with an 404 for specific object' do
expect(json_response['objects'].first).to include(sample_object)
expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it")
end end
end end
context 'when user is blocked' do
let(:role) { :reporter}
let(:user) { create(:user, :blocked)}
it_behaves_like 'LFS http 401 response'
end
end end
context 'when using Deploy Tokens' do context 'when using Deploy Tokens' do
let(:project) { create(:project, :repository) }
let(:authorization) { authorize_deploy_token } let(:authorization) { authorize_deploy_token }
let(:update_user_permissions) { nil } let(:update_user_permissions) { nil }
let(:role) { nil } let(:role) { nil }
...@@ -587,25 +549,19 @@ describe 'Git LFS API and storage' do ...@@ -587,25 +549,19 @@ describe 'Git LFS API and storage' do
context 'when Deploy Token is valid' do context 'when Deploy Token is valid' do
let(:deploy_token) { create(:deploy_token, projects: [project]) } let(:deploy_token) { create(:deploy_token, projects: [project]) }
it_behaves_like 'an authorized requests' it_behaves_like 'an authorized request', renew_authorization: false
end end
context 'when Deploy Token is not valid' do context 'when Deploy Token is not valid' do
let(:deploy_token) { create(:deploy_token, projects: [project], read_repository: false) } let(:deploy_token) { create(:deploy_token, projects: [project], read_repository: false) }
it 'responds with access denied' do it_behaves_like 'LFS http 401 response'
expect(response).to have_gitlab_http_status(401)
end
end end
context 'when Deploy Token is not related to the project' do context 'when Deploy Token is not related to the project' do
let(:another_project) { create(:project, :repository) } let(:deploy_token) { create(:deploy_token, projects: [other_project]) }
let(:deploy_token) { create(:deploy_token, projects: [another_project]) }
it 'responds with access forbidden' do it_behaves_like 'LFS http 404 response'
# We render 404, to prevent data leakage about existence of the project
expect(response).to have_gitlab_http_status(404)
end
end end
end end
...@@ -616,7 +572,7 @@ describe 'Git LFS API and storage' do ...@@ -616,7 +572,7 @@ describe 'Git LFS API and storage' do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
shared_examples 'can download LFS only from own projects' do shared_examples 'can download LFS only from own projects' do |renew_authorization:|
context 'for own project' do context 'for own project' do
let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:pipeline) { create(:ci_empty_pipeline, project: project) }
...@@ -624,11 +580,10 @@ describe 'Git LFS API and storage' do ...@@ -624,11 +580,10 @@ describe 'Git LFS API and storage' do
project.add_reporter(user) project.add_reporter(user)
end end
it_behaves_like 'an authorized requests' it_behaves_like 'an authorized request', renew_authorization: renew_authorization
end end
context 'for other project' do context 'for other project' do
let(:other_project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
it 'rejects downloading code' do it 'rejects downloading code' do
...@@ -641,17 +596,16 @@ describe 'Git LFS API and storage' do ...@@ -641,17 +596,16 @@ describe 'Git LFS API and storage' do
let(:user) { create(:admin) } let(:user) { create(:admin) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do it_behaves_like 'can download LFS only from own projects', renew_authorization: true do
# We render 403, because administrator does have normally access # We render 403, because administrator does have normally access
let(:other_project_status) { 403 } let(:other_project_status) { 403 }
end end
end end
context 'regular user' do context 'regular user' do
let(:user) { create(:user) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do it_behaves_like 'can download LFS only from own projects', renew_authorization: true do
# We render 404, to prevent data leakage about existence of the project # We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 } let(:other_project_status) { 404 }
end end
...@@ -660,7 +614,7 @@ describe 'Git LFS API and storage' do ...@@ -660,7 +614,7 @@ describe 'Git LFS API and storage' do
context 'does not have user' do context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) } let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it_behaves_like 'can download LFS only from own projects' do it_behaves_like 'can download LFS only from own projects', renew_authorization: false do
# We render 404, to prevent data leakage about existence of the project # We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 } let(:other_project_status) { 404 }
end end
...@@ -675,11 +629,9 @@ describe 'Git LFS API and storage' do ...@@ -675,11 +629,9 @@ describe 'Git LFS API and storage' do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with status 200 and href to download' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'responds with status 200 and href to download' do it 'returns href to download' do
expect(json_response).to eq({ expect(json_response).to eq({
'objects' => [ 'objects' => [
{ {
...@@ -688,7 +640,7 @@ describe 'Git LFS API and storage' do ...@@ -688,7 +640,7 @@ describe 'Git LFS API and storage' do
'authenticated' => true, 'authenticated' => true,
'actions' => { 'actions' => {
'download' => { 'download' => {
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", 'href' => objects_url(project, sample_oid),
'header' => {} 'header' => {}
} }
} }
...@@ -703,37 +655,29 @@ describe 'Git LFS API and storage' do ...@@ -703,37 +655,29 @@ describe 'Git LFS API and storage' do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with authorization required' do it_behaves_like 'LFS http 401 response'
expect(response).to have_gitlab_http_status(401)
end
end end
end end
end end
describe 'upload' do describe 'upload' do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:body) do let(:body) { upload_body(sample_object) }
{
'operation' => 'upload',
'objects' => [
{ 'oid' => sample_oid,
'size' => sample_size }
]
}
end
shared_examples 'pushes new LFS objects' do shared_examples 'pushes new LFS objects' do |renew_authorization:|
let(:sample_size) { 150.megabytes } let(:sample_size) { 150.megabytes }
let(:sample_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' } let(:sample_oid) { non_existing_object_oid }
it_behaves_like 'LFS http 200 response'
it 'responds with upload hypermedia link' do it 'responds with upload hypermedia link' do
expect(response).to have_gitlab_http_status(200)
expect(json_response['objects']).to be_kind_of(Array) expect(json_response['objects']).to be_kind_of(Array)
expect(json_response['objects'].first['oid']).to eq(sample_oid) expect(json_response['objects'].first).to include(sample_object)
expect(json_response['objects'].first['size']).to eq(sample_size) expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size))
expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.full_path}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}") expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream')
expect(json_response['objects'].first['actions']['upload']['header']).to eq({ 'Authorization' => authorization, 'Content-Type' => 'application/octet-stream' })
end end
it_behaves_like 'process authorization header', renew_authorization: renew_authorization
end end
describe 'when request is authenticated' do describe 'when request is authenticated' do
...@@ -744,107 +688,80 @@ describe 'Git LFS API and storage' do ...@@ -744,107 +688,80 @@ describe 'Git LFS API and storage' do
project.add_developer(user) project.add_developer(user)
end end
context 'when pushing an lfs object that already exists' do context 'when pushing an LFS object that already exists' do
let(:other_project) { create(:project) }
let(:update_lfs_permissions) do let(:update_lfs_permissions) do
other_project.lfs_objects << lfs_object other_project.lfs_objects << lfs_object
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'responds with links the object to the project' do it 'responds with links the object to the project' do
expect(json_response['objects']).to be_kind_of(Array) expect(json_response['objects']).to be_kind_of(Array)
expect(json_response['objects'].first['oid']).to eq(sample_oid) expect(json_response['objects'].first).to include(sample_object)
expect(json_response['objects'].first['size']).to eq(sample_size)
expect(lfs_object.projects.pluck(:id)).not_to include(project.id) expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
expect(lfs_object.projects.pluck(:id)).to include(other_project.id) expect(lfs_object.projects.pluck(:id)).to include(other_project.id)
expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}") expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size))
expect(json_response['objects'].first['actions']['upload']['header']).to eq({ 'Authorization' => authorization, 'Content-Type' => 'application/octet-stream' }) expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream')
end end
end
context 'when pushing a lfs object that does not exist' do it_behaves_like 'process authorization header', renew_authorization: true
it_behaves_like 'pushes new LFS objects'
end end
context 'when pushing one new and one existing lfs object' do context 'when pushing a LFS object that does not exist' do
let(:body) do it_behaves_like 'pushes new LFS objects', renew_authorization: true
{ end
'operation' => 'upload',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 },
{ 'oid' => sample_oid,
'size' => sample_size }
]
}
end
context 'when pushing one new and one existing LFS object' do
let(:body) { upload_body(multiple_objects) }
let(:update_lfs_permissions) do let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'responds with upload hypermedia link for the new object' do it 'responds with upload hypermedia link for the new object' do
expect(json_response['objects']).to be_kind_of(Array) expect(json_response['objects']).to be_kind_of(Array)
expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") expect(json_response['objects'].first).to include(sample_object)
expect(json_response['objects'].first['size']).to eq(1575078) expect(json_response['objects'].first).not_to have_key('actions')
expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
expect(json_response['objects'].first['actions']['upload']['header']).to eq({ 'Authorization' => authorization, 'Content-Type' => 'application/octet-stream' })
expect(json_response['objects'].last['oid']).to eq(sample_oid) expect(json_response['objects'].last).to include(non_existing_object)
expect(json_response['objects'].last['size']).to eq(sample_size) expect(json_response['objects'].last['actions']['upload']['href']).to eq(objects_url(project, non_existing_object_oid, non_existing_object_size))
expect(json_response['objects'].last).not_to have_key('actions') expect(json_response['objects'].last['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream')
end end
it_behaves_like 'process authorization header', renew_authorization: true
end end
end end
context 'when user does not have push access' do context 'when user does not have push access' do
let(:authorization) { authorize_user } let(:authorization) { authorize_user }
it 'responds with 403' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403)
end
end end
context 'when build is authorized' do context 'when build is authorized' do
let(:authorization) { authorize_ci_project } let(:authorization) { authorize_ci_project }
context 'build has an user' do context 'build has an user' do
let(:user) { create(:user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
context 'tries to push to own project' do context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } it_behaves_like 'LFS http 403 response'
it 'responds with 403 (not 404 because project is public)' do
expect(response).to have_gitlab_http_status(403)
end
end end
context 'tries to push to other project' do context 'tries to push to other project' do
let(:other_project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
# I'm not sure what this tests that is different from the previous test # I'm not sure what this tests that is different from the previous test
it 'responds with 403 (not 404 because project is public)' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403)
end
end end
end end
context 'does not have user' do context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) } let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it 'responds with 403 (not 404 because project is public)' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403)
end
end end
end end
...@@ -856,7 +773,7 @@ describe 'Git LFS API and storage' do ...@@ -856,7 +773,7 @@ describe 'Git LFS API and storage' do
project.deploy_keys_projects.create(deploy_key: key, can_push: true) project.deploy_keys_projects.create(deploy_key: key, can_push: true)
end end
it_behaves_like 'pushes new LFS objects' it_behaves_like 'pushes new LFS objects', renew_authorization: false
end end
end end
...@@ -866,80 +783,60 @@ describe 'Git LFS API and storage' do ...@@ -866,80 +783,60 @@ describe 'Git LFS API and storage' do
project.add_maintainer(user) project.add_maintainer(user)
end end
it 'responds with status 401' do it_behaves_like 'LFS http 401 response'
expect(response).to have_gitlab_http_status(401)
end
end end
context 'when user does not have push access' do context 'when user does not have push access' do
it 'responds with status 401' do it_behaves_like 'LFS http 401 response'
expect(response).to have_gitlab_http_status(401)
end
end end
end end
end end
describe 'unsupported' do describe 'unsupported' do
let(:project) { create(:project) }
let(:authorization) { authorize_user } let(:authorization) { authorize_user }
let(:body) do let(:body) { request_body('other', sample_object) }
{
'operation' => 'other',
'objects' => [
{ 'oid' => sample_oid,
'size' => sample_size }
]
}
end
it 'responds with status 404' do it_behaves_like 'LFS http 404 response'
expect(response).to have_gitlab_http_status(404)
end
end end
end end
describe 'when handling lfs batch request on a read-only GitLab instance' do describe 'when handling LFS batch request on a read-only GitLab instance' do
let(:authorization) { authorize_user } let(:authorization) { authorize_user }
let(:project) { create(:project) }
let(:path) { "#{project.http_url_to_repo}/info/lfs/objects/batch" } subject { post_lfs_json(batch_url(project), body, headers) }
let(:body) do
{ 'objects' => [{ 'oid' => sample_oid, 'size' => sample_size }] }
end
before do before do
allow(Gitlab::Database).to receive(:read_only?) { true } allow(Gitlab::Database).to receive(:read_only?) { true }
project.add_maintainer(user) project.add_maintainer(user)
enable_lfs
subject
end end
it 'responds with a 200 message on download' do context 'when downloading' do
post_lfs_json path, body.merge('operation' => 'download'), headers let(:body) { download_body(sample_object) }
expect(response).to have_gitlab_http_status(200) it_behaves_like 'LFS http 200 response'
end end
it 'responds with a 403 message on upload' do context 'when uploading' do
post_lfs_json path, body.merge('operation' => 'upload'), headers let(:body) { upload_body(sample_object) }
expect(response).to have_gitlab_http_status(403) it_behaves_like 'LFS http expected response code and message' do
expect(json_response).to include('message' => 'You cannot write to this read-only GitLab instance.') let(:response_code) { 403 }
let(:message) { 'You cannot write to this read-only GitLab instance.' }
end
end end
end end
describe 'when pushing a lfs object' do describe 'when pushing a LFS object' do
before do
enable_lfs
end
shared_examples 'unauthorized' do shared_examples 'unauthorized' do
context 'and request is sent by gitlab-workhorse to authorize the request' do context 'and request is sent by gitlab-workhorse to authorize the request' do
before do before do
put_authorize put_authorize
end end
it 'responds with status 401' do it_behaves_like 'LFS http 401 response'
expect(response).to have_gitlab_http_status(401)
end
end end
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
...@@ -947,9 +844,7 @@ describe 'Git LFS API and storage' do ...@@ -947,9 +844,7 @@ describe 'Git LFS API and storage' do
put_finalize put_finalize
end end
it 'responds with status 401' do it_behaves_like 'LFS http 401 response'
expect(response).to have_gitlab_http_status(401)
end
end end
context 'and request is sent with a malformed headers' do context 'and request is sent with a malformed headers' do
...@@ -957,9 +852,7 @@ describe 'Git LFS API and storage' do ...@@ -957,9 +852,7 @@ describe 'Git LFS API and storage' do
put_finalize('/etc/passwd') put_finalize('/etc/passwd')
end end
it 'does not recognize it as a valid lfs command' do it_behaves_like 'LFS http 401 response'
expect(response).to have_gitlab_http_status(401)
end
end end
end end
...@@ -969,9 +862,7 @@ describe 'Git LFS API and storage' do ...@@ -969,9 +862,7 @@ describe 'Git LFS API and storage' do
put_authorize put_authorize
end end
it 'responds with 403' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403)
end
end end
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
...@@ -979,9 +870,7 @@ describe 'Git LFS API and storage' do ...@@ -979,9 +870,7 @@ describe 'Git LFS API and storage' do
put_finalize put_finalize
end end
it 'responds with 403' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403)
end
end end
context 'and request is sent with a malformed headers' do context 'and request is sent with a malformed headers' do
...@@ -989,15 +878,11 @@ describe 'Git LFS API and storage' do ...@@ -989,15 +878,11 @@ describe 'Git LFS API and storage' do
put_finalize('/etc/passwd') put_finalize('/etc/passwd')
end end
it 'does not recognize it as a valid lfs command' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403)
end
end end
end end
describe 'to one project' do describe 'to one project' do
let(:project) { create(:project) }
describe 'when user is authenticated' do describe 'when user is authenticated' do
let(:authorization) { authorize_user } let(:authorization) { authorize_user }
...@@ -1018,9 +903,7 @@ describe 'Git LFS API and storage' do ...@@ -1018,9 +903,7 @@ describe 'Git LFS API and storage' do
put_authorize put_authorize
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'uses the gitlab-workhorse content type' do it 'uses the gitlab-workhorse content type' do
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
...@@ -1029,7 +912,7 @@ describe 'Git LFS API and storage' do ...@@ -1029,7 +912,7 @@ describe 'Git LFS API and storage' do
shared_examples 'a local file' do shared_examples 'a local file' do
it_behaves_like 'a valid response' do it_behaves_like 'a valid response' do
it 'responds with status 200, location of lfs store and object details' do it 'responds with status 200, location of LFS store and object details' do
expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path) expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
expect(json_response['RemoteObject']).to be_nil expect(json_response['RemoteObject']).to be_nil
expect(json_response['LfsOid']).to eq(sample_oid) expect(json_response['LfsOid']).to eq(sample_oid)
...@@ -1049,7 +932,7 @@ describe 'Git LFS API and storage' do ...@@ -1049,7 +932,7 @@ describe 'Git LFS API and storage' do
end end
it_behaves_like 'a valid response' do it_behaves_like 'a valid response' do
it 'responds with status 200, location of lfs remote store and object details' do it 'responds with status 200, location of LFS remote store and object details' do
expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path) expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
expect(json_response['RemoteObject']).to have_key('ID') expect(json_response['RemoteObject']).to have_key('ID')
expect(json_response['RemoteObject']).to have_key('GetURL') expect(json_response['RemoteObject']).to have_key('GetURL')
...@@ -1077,11 +960,9 @@ describe 'Git LFS API and storage' do ...@@ -1077,11 +960,9 @@ describe 'Git LFS API and storage' do
put_finalize put_finalize
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'lfs object is linked to the project' do it 'LFS object is linked to the project' do
expect(lfs_object.projects.pluck(:id)).to include(project.id) expect(lfs_object.projects.pluck(:id)).to include(project.id)
end end
end end
...@@ -1092,7 +973,7 @@ describe 'Git LFS API and storage' do ...@@ -1092,7 +973,7 @@ describe 'Git LFS API and storage' do
end end
end end
context 'and workhorse requests upload finalize for a new lfs object' do context 'and workhorse requests upload finalize for a new LFS object' do
before do before do
lfs_object.destroy lfs_object.destroy
end end
...@@ -1202,33 +1083,25 @@ describe 'Git LFS API and storage' do ...@@ -1202,33 +1083,25 @@ describe 'Git LFS API and storage' do
let(:authorization) { authorize_ci_project } let(:authorization) { authorize_ci_project }
context 'build has an user' do context 'build has an user' do
let(:user) { create(:user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
context 'tries to push to own project' do context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
before do before do
project.add_developer(user) project.add_developer(user)
put_authorize put_authorize
end end
it 'responds with 403 (not 404 because the build user can read the project)' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403)
end
end end
context 'tries to push to other project' do context 'tries to push to other project' do
let(:other_project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
before do before do
put_authorize put_authorize
end end
it 'responds with 404 (do not leak non-public project existence)' do it_behaves_like 'LFS http 404 response'
expect(response).to have_gitlab_http_status(404)
end
end end
end end
...@@ -1239,9 +1112,40 @@ describe 'Git LFS API and storage' do ...@@ -1239,9 +1112,40 @@ describe 'Git LFS API and storage' do
put_authorize put_authorize
end end
it 'responds with 404 (do not leak non-public project existence)' do it_behaves_like 'LFS http 404 response'
expect(response).to have_gitlab_http_status(404) end
end
describe 'when using a user key (LFSToken)' do
let(:authorization) { authorize_user_key }
context 'when user allowed' do
before do
project.add_developer(user)
put_authorize
end
it_behaves_like 'LFS http 200 response'
context 'when user password is expired' do
let(:user) { create(:user, password_expires_at: 1.minute.ago)}
it_behaves_like 'LFS http 401 response'
end
context 'when user is blocked' do
let(:user) { create(:user, :blocked)}
it_behaves_like 'LFS http 401 response'
end
end
context 'when user not allowed' do
before do
put_authorize
end end
it_behaves_like 'LFS http 404 response'
end end
end end
...@@ -1268,11 +1172,9 @@ describe 'Git LFS API and storage' do ...@@ -1268,11 +1172,9 @@ describe 'Git LFS API and storage' do
put_authorize put_authorize
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'with location of lfs store and object details' do it 'with location of LFS store and object details' do
expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path) expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
expect(json_response['LfsOid']).to eq(sample_oid) expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size) expect(json_response['LfsSize']).to eq(sample_size)
...@@ -1284,11 +1186,9 @@ describe 'Git LFS API and storage' do ...@@ -1284,11 +1186,9 @@ describe 'Git LFS API and storage' do
put_finalize put_finalize
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'lfs object is linked to the source project' do it 'LFS object is linked to the source project' do
expect(lfs_object.projects.pluck(:id)).to include(upstream_project.id) expect(lfs_object.projects.pluck(:id)).to include(upstream_project.id)
end end
end end
...@@ -1307,34 +1207,24 @@ describe 'Git LFS API and storage' do ...@@ -1307,34 +1207,24 @@ describe 'Git LFS API and storage' do
end end
context 'build has an user' do context 'build has an user' do
let(:user) { create(:user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
context 'tries to push to own project' do context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } it_behaves_like 'LFS http 403 response'
it 'responds with 403 (not 404 because project is public)' do
expect(response).to have_gitlab_http_status(403)
end
end end
context 'tries to push to other project' do context 'tries to push to other project' do
let(:other_project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
# I'm not sure what this tests that is different from the previous test # I'm not sure what this tests that is different from the previous test
it 'responds with 403 (not 404 because project is public)' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403)
end
end end
end end
context 'does not have user' do context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) } let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it 'responds with 403 (not 404 because project is public)' do it_behaves_like 'LFS http 403 response'
expect(response).to have_gitlab_http_status(403)
end
end end
end end
...@@ -1351,22 +1241,20 @@ describe 'Git LFS API and storage' do ...@@ -1351,22 +1241,20 @@ describe 'Git LFS API and storage' do
upstream_project.lfs_objects << lfs_object upstream_project.lfs_objects << lfs_object
end end
context 'when pushing the same lfs object to the second project' do context 'when pushing the same LFS object to the second project' do
before do before do
finalize_headers = headers finalize_headers = headers
.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file) .merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file)
.merge(workhorse_internal_api_request_header) .merge(workhorse_internal_api_request_header)
put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", put objects_url(second_project, sample_oid, sample_size),
params: {}, params: {},
headers: finalize_headers headers: finalize_headers
end end
it 'responds with status 200' do it_behaves_like 'LFS http 200 response'
expect(response).to have_gitlab_http_status(200)
end
it 'links the lfs object to the project' do it 'links the LFS object to the project' do
expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id) expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id)
end end
end end
...@@ -1377,7 +1265,7 @@ describe 'Git LFS API and storage' do ...@@ -1377,7 +1265,7 @@ describe 'Git LFS API and storage' do
authorize_headers = headers authorize_headers = headers
authorize_headers.merge!(workhorse_internal_api_request_header) if verified authorize_headers.merge!(workhorse_internal_api_request_header) if verified
put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", params: {}, headers: authorize_headers put authorize_url(project, sample_oid, sample_size), params: {}, headers: authorize_headers
end end
def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, args: {}) def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, args: {})
...@@ -1401,42 +1289,11 @@ describe 'Git LFS API and storage' do ...@@ -1401,42 +1289,11 @@ describe 'Git LFS API and storage' do
finalize_headers = headers finalize_headers = headers
finalize_headers.merge!(workhorse_internal_api_request_header) if verified finalize_headers.merge!(workhorse_internal_api_request_header) if verified
put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", params: args, headers: finalize_headers put objects_url(project, sample_oid, sample_size), params: args, headers: finalize_headers
end end
def lfs_tmp_file def lfs_tmp_file
"#{sample_oid}012345678" "#{sample_oid}012345678"
end end
end end
def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
def authorize_ci_project
ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', build.token)
end
def authorize_user
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
end
def authorize_deploy_key
ActionController::HttpAuthentication::Basic.encode_credentials("lfs+deploy-key-#{key.id}", Gitlab::LfsToken.new(key).token)
end
def authorize_user_key
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, Gitlab::LfsToken.new(user).token)
end
def authorize_deploy_token
ActionController::HttpAuthentication::Basic.encode_credentials(deploy_token.username, deploy_token.token)
end
def post_lfs_json(url, body = nil, headers = nil)
params = body.try(:to_json)
headers = (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)
post(url, params: params, headers: headers)
end
end end
# frozen_string_literal: true
require_relative 'workhorse_helpers'
module LfsHttpHelpers
include WorkhorseHelpers
def authorize_ci_project
ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', build.token)
end
def authorize_user
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
end
def authorize_deploy_key
Gitlab::LfsToken.new(key).basic_encoding
end
def authorize_user_key
Gitlab::LfsToken.new(user).basic_encoding
end
def authorize_deploy_token
ActionController::HttpAuthentication::Basic.encode_credentials(deploy_token.username, deploy_token.token)
end
def post_lfs_json(url, body = nil, headers = nil)
params = body.try(:to_json)
headers = (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)
post(url, params: params, headers: headers)
end
def batch_url(project)
"#{project.http_url_to_repo}/info/lfs/objects/batch"
end
def objects_url(project, oid = nil, size = nil)
File.join(["#{project.http_url_to_repo}/gitlab-lfs/objects", oid, size].compact.map(&:to_s))
end
def authorize_url(project, oid, size)
File.join(objects_url(project, oid, size), 'authorize')
end
def download_body(objects)
request_body('download', objects)
end
def upload_body(objects)
request_body('upload', objects)
end
def request_body(operation, objects)
objects = [objects] unless objects.is_a?(Array)
{
'operation' => operation,
'objects' => objects
}
end
end
# frozen_string_literal: true
shared_examples 'LFS http 200 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 200 }
end
end
shared_examples 'LFS http 401 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 401 }
end
end
shared_examples 'LFS http 403 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 403 }
let(:message) { 'Access forbidden. Check your access level.' }
end
end
shared_examples 'LFS http 501 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 501 }
let(:message) { 'Git LFS is not enabled on this GitLab server, contact your admin.' }
end
end
shared_examples 'LFS http 404 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 404 }
end
end
shared_examples 'LFS http expected response code and message' do
let(:response_code) { }
let(:message) { }
it 'responds with the expected response code and message' do
expect(response).to have_gitlab_http_status(response_code)
expect(json_response['message']).to eq(message) if message
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment