mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
0b583c3ccf
This commit extends the previous functionality of *autofetch commits* into internal updating of repository information, which encompasses: * Commit information (changesets) * Disk space information They are now both retrieved when browsing the repository and the latter is fetched asynchronously whenever the last update is older than the timeout setting. This timeout setting is now user-configurable.
410 lines
16 KiB
Ruby
410 lines
16 KiB
Ruby
#-- copyright
|
|
# OpenProject is a project management system.
|
|
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License version 3.
|
|
#
|
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
|
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
|
# Copyright (C) 2010-2013 the ChiliProject Team
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# See doc/COPYRIGHT.rdoc for more details.
|
|
#++
|
|
|
|
require 'spec_helper'
|
|
|
|
module OpenProjectRepositoryAuthenticationSpecs
|
|
describe SysController, type: :controller do
|
|
let(:commit_role) {
|
|
FactoryGirl.create(:role, permissions: [:commit_access,
|
|
:browse_repository])
|
|
}
|
|
let(:browse_role) { FactoryGirl.create(:role, permissions: [:browse_repository]) }
|
|
let(:guest_role) { FactoryGirl.create(:role, permissions: []) }
|
|
let(:valid_user_password) { 'Top Secret Password' }
|
|
let(:valid_user) {
|
|
FactoryGirl.create(:user,
|
|
login: 'johndoe',
|
|
password: valid_user_password,
|
|
password_confirmation: valid_user_password)
|
|
}
|
|
|
|
before(:each) do
|
|
FactoryGirl.create(:non_member, permissions: [:browse_repository])
|
|
DeletedUser.first # creating it first in order to avoid problems with should_receive
|
|
|
|
random_project = FactoryGirl.create(:project, is_public: false)
|
|
@member = FactoryGirl.create(:member,
|
|
user: valid_user,
|
|
roles: [browse_role],
|
|
project: random_project)
|
|
allow(Setting).to receive(:sys_api_key).and_return('12345678')
|
|
allow(Setting).to receive(:sys_api_enabled?).and_return(true)
|
|
allow(Setting).to receive(:repository_authentication_caching_enabled?).and_return(true)
|
|
end
|
|
|
|
describe '#repo_auth', 'for valid login, but no access to repo_auth' do
|
|
before(:each) do
|
|
@key = Setting.sys_api_key
|
|
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(valid_user.login, valid_user_password)
|
|
post 'repo_auth', key: @key, repository: 'without-access', method: 'GET'
|
|
end
|
|
|
|
it 'should respond 403 not allowed' do
|
|
expect(response.code).to eq('403')
|
|
expect(response.body).to eq('Not allowed')
|
|
end
|
|
end
|
|
|
|
describe '#repo_auth', 'for valid login and user has browse repository permission (role reporter) for project' do
|
|
before(:each) do
|
|
@key = Setting.sys_api_key
|
|
@project = FactoryGirl.create(:project, is_public: false)
|
|
@member = FactoryGirl.create(:member,
|
|
user: valid_user,
|
|
roles: [browse_role],
|
|
project: @project)
|
|
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(valid_user.login, valid_user_password)
|
|
end
|
|
|
|
it 'should respond 200 okay dokay for GET' do
|
|
post 'repo_auth', key: @key, repository: @project.identifier, method: 'GET'
|
|
expect(response.code).to eq('200')
|
|
end
|
|
|
|
it 'should respond 403 not allowed for POST' do
|
|
post 'repo_auth', key: @key, repository: @project.identifier, method: 'POST'
|
|
expect(response.code).to eq('403')
|
|
end
|
|
end
|
|
|
|
describe '#repo_auth', 'for valid login and user has commit access permission (role developer) for project' do
|
|
before(:each) do
|
|
@key = Setting.sys_api_key
|
|
@project = FactoryGirl.create(:project, is_public: false)
|
|
@member = FactoryGirl.create(:member,
|
|
user: valid_user,
|
|
roles: [commit_role],
|
|
project: @project)
|
|
valid_user.save
|
|
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(valid_user.login, valid_user_password)
|
|
end
|
|
|
|
it 'should respond 200 okay dokay for GET' do
|
|
post 'repo_auth', key: @key, repository: @project.identifier, method: 'GET'
|
|
expect(response.code).to eq('200')
|
|
end
|
|
|
|
it 'should respond 200 okay dokay for POST' do
|
|
post 'repo_auth', key: @key, repository: @project.identifier, method: 'POST'
|
|
expect(response.code).to eq('200')
|
|
end
|
|
end
|
|
|
|
describe '#repo_auth', 'for invalid login and user has role manager for project' do
|
|
before(:each) do
|
|
@key = Setting.sys_api_key
|
|
@project = FactoryGirl.create(:project, is_public: false)
|
|
@member = FactoryGirl.create(:member,
|
|
user: valid_user,
|
|
roles: [commit_role],
|
|
project: @project)
|
|
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(valid_user.login, valid_user_password + 'made invalid')
|
|
post 'repo_auth', key: @key, repository: @project.identifier, method: 'GET'
|
|
end
|
|
|
|
it 'should respond 401 auth required' do
|
|
expect(response.code).to eq('401')
|
|
end
|
|
end
|
|
|
|
describe '#repo_auth', 'for valid login and user is not member for project' do
|
|
before(:each) do
|
|
@key = Setting.sys_api_key
|
|
@project = FactoryGirl.create(:project, is_public: false)
|
|
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(valid_user.login, valid_user_password)
|
|
post 'repo_auth', key: @key, repository: @project.identifier, method: 'GET'
|
|
end
|
|
|
|
it 'should respond 403 not allowed' do
|
|
expect(response.code).to eq('403')
|
|
end
|
|
end
|
|
|
|
describe '#repo_auth', 'for valid login and project is public' do
|
|
before(:each) do
|
|
@key = Setting.sys_api_key
|
|
@project = FactoryGirl.create(:project, is_public: true)
|
|
|
|
random_project = FactoryGirl.create(:project, is_public: false)
|
|
@member = FactoryGirl.create(:member,
|
|
user: valid_user,
|
|
roles: [browse_role],
|
|
project: random_project)
|
|
|
|
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(valid_user.login, valid_user_password)
|
|
post 'repo_auth', key: @key, repository: @project.identifier, method: 'GET'
|
|
end
|
|
|
|
it 'should respond 200 OK' do
|
|
expect(response.code).to eq('200')
|
|
end
|
|
end
|
|
|
|
describe '#repo_auth', 'for invalid credentials' do
|
|
before(:each) do
|
|
@key = Setting.sys_api_key
|
|
post 'repo_auth', key: @key, repository: 'any-repo', method: 'GET'
|
|
end
|
|
|
|
it 'should respond 401 auth required' do
|
|
expect(response.code).to eq('401')
|
|
expect(response.body).to eq('Authorization required')
|
|
end
|
|
end
|
|
|
|
describe '#repo_auth', 'for invalid api key' do
|
|
before(:each) do
|
|
@key = 'invalid'
|
|
end
|
|
|
|
it 'should respond 403 for valid username/password' do
|
|
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(valid_user.login, valid_user_password)
|
|
post 'repo_auth', key: @key, repository: 'any-repo', method: 'GET'
|
|
expect(response.code).to eq('403')
|
|
expect(response.body).to eq('Access denied. Repository management WS is disabled or key is invalid.')
|
|
end
|
|
|
|
it 'should respond 403 for invalid username/password' do
|
|
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('invalid', 'invalid')
|
|
post 'repo_auth', key: @key, repository: 'any-repo', method: 'GET'
|
|
expect(response.code).to eq('403')
|
|
expect(response.body).to eq('Access denied. Repository management WS is disabled or key is invalid.')
|
|
end
|
|
end
|
|
|
|
before(:each) do
|
|
Rails.cache.clear
|
|
allow(Rails.cache).to receive(:kind_of?).with(anything).and_return(false)
|
|
end
|
|
|
|
describe '#cached_user_login' do
|
|
let(:cache_key) {
|
|
OpenProject::RepositoryAuthentication::CACHE_PREFIX +
|
|
Digest::SHA1.hexdigest("#{valid_user.login}#{valid_user_password}")
|
|
}
|
|
let(:cache_expiry) { OpenProject::RepositoryAuthentication::CACHE_EXPIRES_AFTER }
|
|
|
|
it 'should call user_login only once when called twice' do
|
|
expect(controller).to receive(:user_login).once.and_return(valid_user)
|
|
2.times { controller.send(:cached_user_login, valid_user.login, valid_user_password) }
|
|
end
|
|
|
|
it 'should return the same as user_login for valid creds' do
|
|
expect(controller.send(:cached_user_login, valid_user.login, valid_user_password))
|
|
.to eq(controller.send(:user_login, valid_user.login, valid_user_password))
|
|
end
|
|
|
|
it 'should return the same as user_login for invalid creds' do
|
|
expect(controller.send(:cached_user_login, 'invalid', 'invalid'))
|
|
.to eq(controller.send(:user_login, 'invalid', 'invalid'))
|
|
end
|
|
|
|
it 'should use cache' do
|
|
# allow the cache to return something reasonable for
|
|
# other requests, while ensuring that it is not queried
|
|
# with the cache key in question
|
|
|
|
# unfortunately, and_call_original currently fails
|
|
allow(Rails.cache).to receive(:fetch) do |*args|
|
|
expect(args.first).not_to eq(cache_key)
|
|
|
|
name = args.first.split('/').last
|
|
Marshal.dump(Setting.send(:find_or_default, name).value)
|
|
end
|
|
# Rails.cache.should_receive(:fetch).with(anything).and_call_original
|
|
expect(Rails.cache).to receive(:fetch).with(cache_key, expires_in: cache_expiry) \
|
|
.and_return(Marshal.dump(valid_user.id.to_s))
|
|
controller.send(:cached_user_login, valid_user.login, valid_user_password)
|
|
end
|
|
|
|
describe 'with caching disabled' do
|
|
before do
|
|
allow(Setting).to receive(:repository_authentication_caching_enabled?).and_return(false)
|
|
end
|
|
|
|
it 'should not use a cache' do
|
|
# allow the cache to return something reasonable for
|
|
# other requests, while ensuring that it is not queried
|
|
# with the cache key in question
|
|
#
|
|
# unfortunately, and_call_original currently fails
|
|
allow(Rails.cache).to receive(:fetch) do |*args|
|
|
expect(args.first).not_to eq(cache_key)
|
|
|
|
name = args.first.split('/').last
|
|
Marshal.dump(Setting.send(:find_or_default, name).value)
|
|
end
|
|
|
|
controller.send(:cached_user_login, valid_user.login, valid_user_password)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'update_required_storage' do
|
|
let(:force) { nil }
|
|
let(:apikey) { Setting.sys_api_key }
|
|
let(:last_updated) { nil }
|
|
|
|
def request_storage
|
|
get 'update_required_storage', key: apikey, id: id, force: force
|
|
end
|
|
|
|
context 'missing project' do
|
|
let(:id) { 1234 }
|
|
|
|
it 'returns 404' do
|
|
request_storage
|
|
expect(response.code).to eq('404')
|
|
expect(response.body).to include('Could not find project #1234')
|
|
end
|
|
end
|
|
|
|
context 'available project, but missing repository' do
|
|
let(:project) { FactoryGirl.build_stubbed(:project) }
|
|
let(:id) { project.id }
|
|
before do
|
|
allow(Project).to receive(:find).and_return(project)
|
|
request_storage
|
|
end
|
|
|
|
it 'returns 404' do
|
|
expect(response.code).to eq('404')
|
|
expect(response.body).to include("Project ##{project.id} does not have a repository.")
|
|
end
|
|
end
|
|
|
|
context 'stubbed repository' do
|
|
let(:project) { FactoryGirl.build_stubbed(:project) }
|
|
let(:id) { project.id }
|
|
let(:repository) {
|
|
FactoryGirl.build_stubbed(:repository_subversion, url: url, root_url: url)
|
|
}
|
|
|
|
before do
|
|
allow(Project).to receive(:find).and_return(project)
|
|
allow(project).to receive(:repository).and_return(repository)
|
|
|
|
allow(repository).to receive(:storage_updated_at).and_return(last_updated)
|
|
request_storage
|
|
end
|
|
|
|
context 'local non-existing repository' do
|
|
let(:root_url) { '/tmp/does/not/exist/svn/foo.svn' }
|
|
let(:url) { "file://#{root_url}" }
|
|
|
|
it 'does not have storage available' do
|
|
expect(repository.scm.storage_available?).to be false
|
|
expect(response.code).to eq('400')
|
|
end
|
|
end
|
|
|
|
context 'remote stubbed repository' do
|
|
let(:root_url) { '' }
|
|
let(:url) { 'https://foo.example.org/svn/bar' }
|
|
|
|
it 'has no storage available' do
|
|
request_storage
|
|
expect(repository.scm.storage_available?).to be false
|
|
expect(response.code).to eq('400')
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'local existing repository' do
|
|
with_subversion_repository do |repo_dir|
|
|
let(:root_url) { repo_dir }
|
|
let(:url) { "file://#{root_url}" }
|
|
|
|
let(:project) { FactoryGirl.create(:project) }
|
|
let(:id) { project.id }
|
|
let(:repository) {
|
|
FactoryGirl.create(:repository_subversion, project: project, url: url, root_url: url)
|
|
}
|
|
|
|
before do
|
|
allow(Project).to receive(:find).and_return(project)
|
|
allow(project).to receive(:repository).and_return(repository)
|
|
allow(repository).to receive(:storage_updated_at).and_return(last_updated)
|
|
end
|
|
|
|
it 'has storage available' do
|
|
expect(repository.scm.storage_available?).to be true
|
|
end
|
|
|
|
context 'storage never updated before' do
|
|
it 'updates the storage' do
|
|
expect(repository.required_storage_bytes).to be == 0
|
|
request_storage
|
|
|
|
expect(response.code).to eq('200')
|
|
expect(response.body).to include('Updated: true')
|
|
|
|
repository.reload
|
|
expect(repository.required_storage_bytes).to be > 0
|
|
end
|
|
end
|
|
|
|
context 'outdated storage' do
|
|
let(:last_updated) { 2.days.ago }
|
|
it 'updates the storage' do
|
|
expect(Delayed::Job)
|
|
.to receive(:enqueue).with(instance_of(::Scm::StorageUpdaterJob))
|
|
|
|
request_storage
|
|
end
|
|
end
|
|
|
|
context 'valid storage time' do
|
|
let(:last_updated) { 10.minutes.ago }
|
|
|
|
it 'does not update to storage' do
|
|
expect(Delayed::Job)
|
|
.not_to receive(:enqueue).with(instance_of(::Scm::StorageUpdaterJob))
|
|
|
|
request_storage
|
|
end
|
|
end
|
|
|
|
context 'valid storage time and force' do
|
|
let(:force) { '1' }
|
|
let(:last_updated) { 10.minutes.ago }
|
|
|
|
it 'does update to storage' do
|
|
expect(Delayed::Job)
|
|
.to receive(:enqueue).with(instance_of(::Scm::StorageUpdaterJob))
|
|
|
|
request_storage
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|