gitlab-org--gitlab-foss/spec/requests/git_http_spec.rb
Lin Jen-Shin 0f0738e788 Merge remote-tracking branch 'upstream/master' into feature/1376-allow-write-access-deploy-keys
* upstream/master: (538 commits)
  Reject blank environment vcariables in Gitlab::Git::RevList
  Add online terminal documentation
  Add changelog entry
  Add terminal UI and controller actions
  Fix specs
  Even out padding on plus button in breadcrumb menu
  Update font size of detail page header to 14px
  Update CHANGELOG.md for 8.13.10
  Update CHANGELOG.md for 8.14.5
  Fix Route#rename_children behavior
  Remove inline-block styling from status
  Add terminals to the Kubernetes deployment service
  Add a ReactiveCaching concern for use in the KubernetesService
  Add xterm.js 2.1.0 and a wrapper class to the asset pipeline
  Remove unnecessary hidden svg elements for icons.
  Fix consistent typo in environment.js
  Use a block to insert extra check for authenticate_build!
  Align milestone column header with count number
  Add Wiki import to BB importer
  Make CI badge hitboxes better match container
  ...
2016-12-20 20:54:40 +08:00

563 lines
20 KiB
Ruby

require "spec_helper"
describe 'Git HTTP requests', lib: true do
include GitHttpHelpers
include WorkhorseHelpers
it "gives WWW-Authenticate hints" do
clone_get('doesnt/exist.git')
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end
describe "User with no identities" do
let(:user) { create(:user) }
let(:project) { create(:project, path: 'project.git-project') }
context "when the project doesn't exist" do
context "when no authentication is provided" do
it "responds with status 401 (no project existence information leak)" do
download('doesnt/exist.git') do |response|
expect(response).to have_http_status(401)
end
end
end
context "when username and password are provided" do
context "when authentication fails" do
it "responds with status 401" do
download('doesnt/exist.git', user: user.username, password: "nope") do |response|
expect(response).to have_http_status(401)
end
end
end
context "when authentication succeeds" do
it "responds with status 404" do
download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
expect(response).to have_http_status(404)
end
end
end
end
end
context "when the Wiki for a project exists" do
it "responds with the right project" do
wiki = ProjectWiki.new(project)
project.update_attribute(:visibility_level, Project::PUBLIC)
download("/#{wiki.repository.path_with_namespace}.git") do |response|
json_body = ActiveSupport::JSON.decode(response.body)
expect(response).to have_http_status(200)
expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end
end
context "when the project exists" do
let(:path) { "#{project.path_with_namespace}.git" }
context "when the project is public" do
before do
project.update_attribute(:visibility_level, Project::PUBLIC)
end
it "downloads get status 200" do
download(path, {}) do |response|
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end
it "uploads get status 401" do
upload(path, {}) do |response|
expect(response).to have_http_status(401)
end
end
context "with correct credentials" do
let(:env) { { user: user.username, password: user.password } }
it "uploads get status 403" do
upload(path, env) do |response|
expect(response).to have_http_status(403)
end
end
context 'but git-receive-pack is disabled' do
it "responds with status 404" do
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
upload(path, env) do |response|
expect(response).to have_http_status(403)
end
end
end
end
context 'but git-upload-pack is disabled' do
it "responds with status 404" do
allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
download(path, {}) do |response|
expect(response).to have_http_status(404)
end
end
end
context 'when the request is not from gitlab-workhorse' do
it 'raises an exception' do
expect do
get("/#{project.path_with_namespace}.git/info/refs?service=git-upload-pack")
end.to raise_error(JWT::DecodeError)
end
end
context 'when the repo is public' do
context 'but the repo is disabled' do
it 'does not allow to clone the repo' do
project = create(:project, :public, repository_access_level: ProjectFeature::DISABLED)
download("#{project.path_with_namespace}.git", {}) do |response|
expect(response).to have_http_status(:unauthorized)
end
end
end
context 'but the repo is enabled' do
it 'allows to clone the repo' do
project = create(:project, :public, repository_access_level: ProjectFeature::ENABLED)
download("#{project.path_with_namespace}.git", {}) do |response|
expect(response).to have_http_status(:ok)
end
end
end
context 'but only project members are allowed' do
it 'does not allow to clone the repo' do
project = create(:project, :public, repository_access_level: ProjectFeature::PRIVATE)
download("#{project.path_with_namespace}.git", {}) do |response|
expect(response).to have_http_status(:unauthorized)
end
end
end
end
end
context "when the project is private" do
before do
project.update_attribute(:visibility_level, Project::PRIVATE)
end
context "when no authentication is provided" do
it "responds with status 401 to downloads" do
download(path, {}) do |response|
expect(response).to have_http_status(401)
end
end
it "responds with status 401 to uploads" do
upload(path, {}) do |response|
expect(response).to have_http_status(401)
end
end
end
context "when username and password are provided" do
let(:env) { { user: user.username, password: 'nope' } }
context "when authentication fails" do
it "responds with status 401" do
download(path, env) do |response|
expect(response).to have_http_status(401)
end
end
context "when the user is IP banned" do
it "responds with status 401" do
expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4')
clone_get(path, env)
expect(response).to have_http_status(401)
end
end
end
context "when authentication succeeds" do
let(:env) { { user: user.username, password: user.password } }
context "when the user has access to the project" do
before do
project.team << [user, :master]
end
context "when the user is blocked" do
it "responds with status 404" do
user.block
project.team << [user, :master]
download(path, env) do |response|
expect(response).to have_http_status(404)
end
end
end
context "when the user isn't blocked" do
it "downloads get status 200" do
expect(Rack::Attack::Allow2Ban).to receive(:reset)
clone_get(path, env)
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it "uploads get status 200" do
upload(path, env) do |response|
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end
end
context "when an oauth token is provided" do
before do
application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
@token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
end
it "downloads get status 200" do
clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it "uploads get status 401 (no project existence information leak)" do
push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
expect(response).to have_http_status(401)
end
end
context 'when user has 2FA enabled' do
let(:user) { create(:user, :two_factor) }
let(:access_token) { create(:personal_access_token, user: user) }
before do
project.team << [user, :master]
end
context 'when username and password are provided' do
it 'rejects the clone attempt' do
download("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response|
expect(response).to have_http_status(401)
expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
end
end
it 'rejects the push attempt' do
upload("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response|
expect(response).to have_http_status(401)
expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
end
end
end
context 'when username and personal access token are provided' do
it 'allows clones' do
download("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response|
expect(response).to have_http_status(200)
end
end
it 'allows pushes' do
upload("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response|
expect(response).to have_http_status(200)
end
end
end
end
context "when blank password attempts follow a valid login" do
def attempt_login(include_password)
password = include_password ? user.password : ""
clone_get path, user: user.username, password: password
response.status
end
it "repeated attempts followed by successful attempt" do
options = Gitlab.config.rack_attack.git_basic_auth
maxretry = options[:maxretry] - 1
ip = '1.2.3.4'
allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
Rack::Attack::Allow2Ban.reset(ip, options)
maxretry.times.each do
expect(attempt_login(false)).to eq(401)
end
expect(attempt_login(true)).to eq(200)
expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
maxretry.times.each do
expect(attempt_login(false)).to eq(401)
end
Rack::Attack::Allow2Ban.reset(ip, options)
end
end
end
context "when the user doesn't have access to the project" do
it "downloads get status 404" do
download(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(404)
end
end
it "uploads get status 404" do
upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(404)
end
end
end
end
end
context "when a gitlab ci token is provided" do
let(:build) { create(:ci_build, :running) }
let(:project) { build.project }
let(:other_project) { create(:empty_project) }
before do
project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED)
end
context 'when build created by system is authenticated' do
it "downloads get status 200" do
clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it "uploads get status 401 (no project existence information leak)" do
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(401)
end
it "downloads from other project get status 404" do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(404)
end
end
context 'and build created by' do
before do
build.update(user: user)
project.team << [user, :reporter]
end
shared_examples 'can download code only' do
it 'downloads get status 200' do
allow_any_instance_of(Repository).
to receive(:exists?).and_return(true)
clone_get "#{project.path_with_namespace}.git",
user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it 'downloads from non-existing repository and gets 403' do
allow_any_instance_of(Repository).
to receive(:exists?).and_return(false)
clone_get "#{project.path_with_namespace}.git",
user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(403)
end
it 'uploads get status 403' do
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(401)
end
end
context 'administrator' do
let(:user) { create(:admin) }
it_behaves_like 'can download code only'
it 'downloads from other project get status 403' do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(403)
end
end
context 'regular user' do
let(:user) { create(:user) }
it_behaves_like 'can download code only'
it 'downloads from other project get status 404' do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(404)
end
end
end
end
end
end
context "when the project path doesn't end in .git" do
context "GET info/refs" do
let(:path) { "/#{project.path_with_namespace}/info/refs" }
context "when no params are added" do
before { get path }
it "redirects to the .git suffix version" do
expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs")
end
end
context "when the upload-pack service is requested" do
let(:params) { { service: 'git-upload-pack' } }
before { get path, params }
it "redirects to the .git suffix version" do
expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
end
end
context "when the receive-pack service is requested" do
let(:params) { { service: 'git-receive-pack' } }
before { get path, params }
it "redirects to the .git suffix version" do
expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
end
end
context "when the params are anything else" do
let(:params) { { service: 'git-implode-pack' } }
before { get path, params }
it "redirects to the sign-in page" do
expect(response).to redirect_to(new_user_session_path)
end
end
end
context "POST git-upload-pack" do
it "fails to find a route" do
expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
end
end
context "POST git-receive-pack" do
it "failes to find a route" do
expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
end
end
end
context "retrieving an info/refs file" do
before { project.update_attribute(:visibility_level, Project::PUBLIC) }
context "when the file exists" do
before do
# Provide a dummy file in its place
allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do
Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt')
end
get "/#{project.path_with_namespace}/blob/master/info/refs"
end
it "returns the file" do
expect(response).to have_http_status(200)
end
end
context "when the file does not exist" do
before { get "/#{project.path_with_namespace}/blob/master/info/refs" }
it "returns not found" do
expect(response).to have_http_status(404)
end
end
end
end
describe "User with LDAP identity" do
let(:user) { create(:omniauth_user, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
allow(Gitlab::LDAP::Authentication).to receive(:login).and_return(nil)
allow(Gitlab::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user)
end
context "when authentication fails" do
context "when no authentication is provided" do
it "responds with status 401" do
download('doesnt/exist.git') do |response|
expect(response).to have_http_status(401)
end
end
end
context "when username and invalid password are provided" do
it "responds with status 401" do
download('doesnt/exist.git', user: user.username, password: "nope") do |response|
expect(response).to have_http_status(401)
end
end
end
end
context "when authentication succeeds" do
context "when the project doesn't exist" do
it "responds with status 404" do
download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
expect(response).to have_http_status(404)
end
end
end
context "when the project exists" do
let(:project) { create(:project, path: 'project.git-project') }
before do
project.team << [user, :master]
end
it "responds with status 200" do
clone_get(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(200)
end
end
end
end
end
end