diff options
Diffstat (limited to 'spec')
31 files changed, 728 insertions, 395 deletions
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb index 5fc5cdfc9b9..0e25f6a96d7 100644 --- a/spec/controllers/oauth/authorizations_controller_spec.rb +++ b/spec/controllers/oauth/authorizations_controller_spec.rb @@ -70,76 +70,59 @@ RSpec.describe Oauth::AuthorizationsController do describe 'GET #new' do subject { get :new, params: params } - include_examples 'OAuth Authorizations require confirmed user' include_examples "Implicit grant can't be used in confidential application" - context 'rendering of views based on the ownership of the application' do - shared_examples 'render views' do - render_views - - it 'returns 200 and renders view with correct info', :aggregate_failures do - subject + context 'when the user is confirmed' do + let(:confirmed_at) { 1.hour.ago } - expect(response).to have_gitlab_http_status(:ok) - expect(response.body).to include(application.owner.name) - expect(response).to render_template('doorkeeper/authorizations/new') - end - end + context 'when there is already an access token for the application with a matching scope' do + before do + scopes = Doorkeeper::OAuth::Scopes.from_string('api') - subject { get :new, params: params } + allow(Doorkeeper.configuration).to receive(:scopes).and_return(scopes) - context 'when auth app owner is a user' do - context 'with valid params' do - it_behaves_like 'render views' + create(:oauth_access_token, application: application, resource_owner_id: user.id, scopes: scopes) end - end - - context 'when auth app owner is a group' do - let(:group) { create(:group) } - context 'when auth app owner is a root group' do - let(:application) { create(:oauth_application, owner_id: group.id, owner_type: 'Namespace') } + it 'authorizes the request and shows the user a page that redirects' do + subject - it_behaves_like 'render views' + expect(request.session['user_return_to']).to be_nil + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template('doorkeeper/authorizations/redirect') end + end - context 'when auth app owner is a subgroup' do - let(:subgroup) { create(:group, parent: group) } - let(:application) { create(:oauth_application, owner_id: subgroup.id, owner_type: 'Namespace') } + context 'without valid params' do + it 'returns 200 code and renders error view' do + get :new - it_behaves_like 'render views' + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template('doorkeeper/authorizations/error') end end - context 'when there is no owner associated' do - let(:application) { create(:oauth_application, owner_id: nil, owner_type: nil) } + context 'with valid params' do + render_views - it 'renders view' do + it 'returns 200 code and renders view' do subject expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template('doorkeeper/authorizations/new') end - end - end - context 'without valid params' do - it 'returns 200 code and renders error view' do - get :new + it 'deletes session.user_return_to and redirects when skip authorization' do + application.update!(trusted: true) + request.session['user_return_to'] = 'http://example.com' - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template('doorkeeper/authorizations/error') - end - end - - it 'deletes session.user_return_to and redirects when skip authorization' do - application.update!(trusted: true) - request.session['user_return_to'] = 'http://example.com' - - subject + subject - expect(request.session['user_return_to']).to be_nil - expect(response).to have_gitlab_http_status(:found) + expect(request.session['user_return_to']).to be_nil + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template('doorkeeper/authorizations/redirect') + end + end end end diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb index 072a5f1f402..148ee64fb08 100644 --- a/spec/factories/environments.rb +++ b/spec/factories/environments.rb @@ -16,19 +16,19 @@ FactoryBot.define do end trait :production do - tier { :production } + name { 'production' } end trait :staging do - tier { :staging } + name { 'staging' } end trait :testing do - tier { :testing } + name { 'testing' } end trait :development do - tier { :development } + name { 'development' } end trait :with_review_app do |environment| diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index e60d9d6ab69..1d7099ba443 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -394,6 +394,25 @@ RSpec.describe 'Login' do gitlab_sign_in(user) end + + context 'when the users password is expired' do + before do + user.update!(password_expires_at: Time.parse('2018-05-08 11:29:46 UTC')) + end + + it 'asks for a new password' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + + visit new_user_session_path + + fill_in 'user_login', with: user.email + fill_in 'user_password', with: '12345678' + click_button 'Sign in' + + expect(current_path).to eq(new_profile_password_path) + end + end end context 'with invalid username and password' do diff --git a/spec/frontend/notebook/cells/markdown_spec.js b/spec/frontend/notebook/cells/markdown_spec.js index d250ffed1a9..deeee5d6589 100644 --- a/spec/frontend/notebook/cells/markdown_spec.js +++ b/spec/frontend/notebook/cells/markdown_spec.js @@ -39,7 +39,7 @@ describe('Markdown component', () => { expect(vm.$el.querySelector('.markdown h1')).not.toBeNull(); }); - it('sanitizes output', async () => { + it('sanitizes Markdown output', async () => { Object.assign(cell, { source: [ '[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+Cg==)\n', @@ -50,6 +50,17 @@ describe('Markdown component', () => { expect(vm.$el.querySelector('a').getAttribute('href')).toBeNull(); }); + it('sanitizes HTML', async () => { + const findLink = () => vm.$el.querySelector('.xss-link'); + Object.assign(cell, { + source: ['<a href="test.js" data-remote=true data-type="script" class="xss-link">XSS</a>\n'], + }); + + await vm.$nextTick(); + expect(findLink().getAttribute('data-remote')).toBe(null); + expect(findLink().getAttribute('data-type')).toBe(null); + }); + describe('tables', () => { beforeEach(() => { json = getJSONFixture('blob/notebook/markdown-table.json'); diff --git a/spec/graphql/resolvers/timelog_resolver_spec.rb b/spec/graphql/resolvers/timelog_resolver_spec.rb index 585cd657e35..bb4938c751f 100644 --- a/spec/graphql/resolvers/timelog_resolver_spec.rb +++ b/spec/graphql/resolvers/timelog_resolver_spec.rb @@ -11,26 +11,27 @@ RSpec.describe Resolvers::TimelogResolver do context "with a group" do let_it_be(:current_user) { create(:user) } - let_it_be(:group) { create(:group) } - let_it_be(:project) { create(:project, :public, group: group) } - - before_all do - group.add_developer(current_user) - project.add_developer(current_user) - end - - before do - group.clear_memoization(:timelogs) - end + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :empty_repo, :public, group: group) } describe '#resolve' do + let_it_be(:short_time_ago) { 5.days.ago.beginning_of_day } + let_it_be(:medium_time_ago) { 15.days.ago.beginning_of_day } + let_it_be(:issue) { create(:issue, project: project) } - let_it_be(:issue2) { create(:issue, project: project) } - let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: 2.days.ago.beginning_of_day) } - let_it_be(:timelog2) { create(:issue_timelog, issue: issue2, spent_at: 2.days.ago.end_of_day) } - let_it_be(:timelog3) { create(:issue_timelog, issue: issue2, spent_at: 10.days.ago) } + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + + let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.beginning_of_day) } + let_it_be(:timelog2) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.end_of_day) } + let_it_be(:timelog3) { create(:merge_request_timelog, merge_request: merge_request, spent_at: medium_time_ago) } + + let(:args) { { start_time: short_time_ago, end_time: short_time_ago.noon } } + + it 'finds all timelogs' do + timelogs = resolve_timelogs - let(:args) { { start_time: 6.days.ago, end_time: 2.days.ago.noon } } + expect(timelogs).to contain_exactly(timelog1, timelog2, timelog3) + end it 'finds all timelogs within given dates' do timelogs = resolve_timelogs(**args) @@ -38,15 +39,28 @@ RSpec.describe Resolvers::TimelogResolver do expect(timelogs).to contain_exactly(timelog1) end - it 'return nothing when user has insufficient permissions' do - user = create(:user) - group.add_guest(current_user) + context 'when only start_date is present' do + let(:args) { { start_date: short_time_ago } } + + it 'finds timelogs until the end of day of end_date' do + timelogs = resolve_timelogs(**args) + + expect(timelogs).to contain_exactly(timelog1, timelog2) + end + end + + context 'when only end_date is present' do + let(:args) { { end_date: medium_time_ago } } + + it 'finds timelogs until the end of day of end_date' do + timelogs = resolve_timelogs(**args) - expect(resolve_timelogs(user: user, **args)).to be_empty + expect(timelogs).to contain_exactly(timelog3) + end end context 'when start_time and end_date are present' do - let(:args) { { start_time: 6.days.ago, end_date: 2.days.ago } } + let(:args) { { start_time: short_time_ago, end_date: short_time_ago } } it 'finds timelogs until the end of day of end_date' do timelogs = resolve_timelogs(**args) @@ -56,7 +70,7 @@ RSpec.describe Resolvers::TimelogResolver do end context 'when start_date and end_time are present' do - let(:args) { { start_date: 6.days.ago, end_time: 2.days.ago.noon } } + let(:args) { { start_date: short_time_ago, end_time: short_time_ago.noon } } it 'finds all timelogs within start_date and end_time' do timelogs = resolve_timelogs(**args) @@ -68,95 +82,32 @@ RSpec.describe Resolvers::TimelogResolver do context 'when arguments are invalid' do let_it_be(:error_class) { Gitlab::Graphql::Errors::ArgumentError } - context 'when no time or date arguments are present' do - let(:args) { {} } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Start and End arguments must be present/) - end - end - - context 'when only start_time is present' do - let(:args) { { start_time: 6.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - - context 'when only end_time is present' do - let(:args) { { end_time: 2.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - - context 'when only start_date is present' do - let(:args) { { start_date: 6.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - - context 'when only end_date is present' do - let(:args) { { end_date: 2.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - context 'when start_time and start_date are present' do - let(:args) { { start_time: 6.days.ago, start_date: 6.days.ago } } + let(:args) { { start_time: short_time_ago, start_date: short_time_ago } } it 'returns correct error' do expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) + .to raise_error(error_class, /Provide either a start date or time, but not both/) end end context 'when end_time and end_date are present' do - let(:args) { { end_time: 2.days.ago, end_date: 2.days.ago } } + let(:args) { { end_time: short_time_ago, end_date: short_time_ago } } it 'returns correct error' do expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - - context 'when three arguments are present' do - let(:args) { { start_date: 6.days.ago, end_date: 2.days.ago, end_time: 2.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Only Time or Date arguments must be present/) + .to raise_error(error_class, /Provide either an end date or time, but not both/) end end context 'when start argument is after end argument' do - let(:args) { { start_time: 2.days.ago, end_time: 6.days.ago } } + let(:args) { { start_time: short_time_ago, end_time: medium_time_ago } } it 'returns correct error' do expect { resolve_timelogs(**args) } .to raise_error(error_class, /Start argument must be before End argument/) end end - - context 'when time range is more than 60 days' do - let(:args) { { start_time: 3.months.ago, end_time: 2.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /The time range period cannot contain more than 60 days/) - end - end end end end diff --git a/spec/graphql/types/timelog_type_spec.rb b/spec/graphql/types/timelog_type_spec.rb index 791c2fdb046..1344af89fb6 100644 --- a/spec/graphql/types/timelog_type_spec.rb +++ b/spec/graphql/types/timelog_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Timelog'] do it { expect(described_class.graphql_name).to eq('Timelog') } it { expect(described_class).to have_graphql_fields(fields) } - it { expect(described_class).to require_graphql_authorizations(:read_group_timelogs) } + it { expect(described_class).to require_graphql_authorizations(:read_issue) } describe 'user field' do subject { described_class.fields['user'] } diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 00a59f037e0..e946857ac77 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -418,6 +418,13 @@ FooBar describe '#markup' do let(:content) { 'Noël' } + it 'sets the :text_source to :blob in the context' do + context = {} + helper.markup('foo.md', content, context) + + expect(context).to include(text_source: :blob) + end + it 'preserves encoding' do expect(content.encoding.name).to eq('UTF-8') expect(helper.markup('foo.rst', content).encoding.name).to eq('UTF-8') diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb index abb1673bb88..421f6373eff 100644 --- a/spec/initializers/lograge_spec.rb +++ b/spec/initializers/lograge_spec.rb @@ -173,6 +173,27 @@ RSpec.describe 'lograge', type: :request do end end + describe 'with access token in url' do + before do + event.payload[:location] = 'http://example.com/auth.html#access_token=secret_token&token_type=Bearer' + end + + it 'strips location from sensitive information' do + subscriber.redirect_to(event) + subscriber.process_action(event) + + expect(log_data['location']).not_to include('secret_token') + expect(log_data['location']).to include('filtered') + end + + it 'leaves non-sensitive information from location' do + subscriber.redirect_to(event) + subscriber.process_action(event) + + expect(log_data['location']).to include('&token_type=Bearer') + end + end + context 'with db payload' do context 'when RequestStore is enabled', :request_store do it 'includes db counters' do diff --git a/spec/lib/banzai/filter/truncate_source_filter_spec.rb b/spec/lib/banzai/filter/truncate_source_filter_spec.rb index d5eb8b738b1..8970aa1d382 100644 --- a/spec/lib/banzai/filter/truncate_source_filter_spec.rb +++ b/spec/lib/banzai/filter/truncate_source_filter_spec.rb @@ -8,24 +8,68 @@ RSpec.describe Banzai::Filter::TruncateSourceFilter do let(:short_text) { 'foo' * 10 } let(:long_text) { ([short_text] * 10).join(' ') } - it 'does nothing when limit is unspecified' do - output = filter(long_text) - - expect(output).to eq(long_text) + before do + stub_const("#{described_class}::CHARACTER_COUNT_LIMIT", 50) + stub_const("#{described_class}::USER_MSG_LIMIT", 20) end - it 'does nothing to a short-enough text' do - output = filter(short_text, limit: short_text.bytesize) + context 'when markdown belongs to a blob' do + it 'does nothing when limit is unspecified' do + output = filter(long_text, text_source: :blob) + + expect(output).to eq(long_text) + end + + it 'truncates normally when limit specified' do + truncated = 'foofoof...' + + output = filter(long_text, text_source: :blob, limit: 10) - expect(output).to eq(short_text) + expect(output).to eq(truncated) + end end - it 'truncates UTF-8 text by bytes, on a character boundary' do - utf8_text = '日本語の文字が大きい' - truncated = '日...' + context 'when markdown belongs to a field (non-blob)' do + it 'does nothing when limit is greater' do + output = filter(long_text, limit: 1.megabyte) + + expect(output).to eq(long_text) + end + + it 'truncates to the default when limit is unspecified' do + stub_const("#{described_class}::USER_MSG_LIMIT", 200) + truncated = 'foofoofoofoofoofoofoofoofoofoo foofoofoofoofoof...' + + output = filter(long_text) + + expect(output).to eq(truncated) + end + + it 'prepends the user message' do + truncated = <<~TEXT + _The text is longer than 50 characters and has been visually truncated._ + + foofoofoofoofoofoofoofoofoofoo foofoofoofoofoof... + TEXT + + output = filter(long_text) + + expect(output).to eq(truncated.strip) + end + + it 'does nothing to a short-enough text' do + output = filter(short_text, limit: short_text.bytesize) + + expect(output).to eq(short_text) + end + + it 'truncates UTF-8 text by bytes, on a character boundary' do + utf8_text = '日本語の文字が大きい' + truncated = '日...' - expect(filter(utf8_text, limit: truncated.bytesize)).to eq(truncated) - expect(filter(utf8_text, limit: utf8_text.bytesize)).to eq(utf8_text) - expect(filter(utf8_text, limit: utf8_text.mb_chars.size)).not_to eq(utf8_text) + expect(filter(utf8_text, limit: truncated.bytesize)).to eq(truncated) + expect(filter(utf8_text, limit: utf8_text.bytesize)).to eq(utf8_text) + expect(filter(utf8_text, limit: utf8_text.mb_chars.size)).not_to eq(utf8_text) + end end end diff --git a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb index 5a3ceccfae1..e8df395564a 100644 --- a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb @@ -36,9 +36,11 @@ RSpec.describe Banzai::Pipeline::PostProcessPipeline do end let(:doc) { HTML::Pipeline.parse(html) } + let(:non_related_xpath_calls) { 2 } it 'searches for attributes only once' do - expect(doc).to receive(:search).once.and_call_original + expect(doc).to receive(:xpath).exactly(non_related_xpath_calls + 1).times + .and_call_original subject end diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb index d3c6cde5590..102d6fba97f 100644 --- a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb +++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb @@ -57,5 +57,13 @@ RSpec.describe Gitlab::Auth::UserAccessDeniedReason do it { is_expected.to eq('Your account is pending approval from your administrator and hence blocked.') } end + + context 'when the user has expired password' do + before do + user.update!(password_expires_at: 2.days.ago) + end + + it { is_expected.to eq('Your password expired. Please access GitLab from a web browser to update your password.') } + end end end diff --git a/spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb b/spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb new file mode 100644 index 00000000000..e14328b6150 --- /dev/null +++ b/spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::UpdateUsersWhereTwoFactorAuthRequiredFromGroup, :migration, schema: 20210519154058 do + include MigrationHelpers::NamespacesHelpers + + let(:group_with_2fa_parent) { create_namespace('parent', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true) } + let(:group_with_2fa_child) { create_namespace('child', Gitlab::VisibilityLevel::PRIVATE, parent_id: group_with_2fa_parent.id) } + let(:members_table) { table(:members) } + let(:users_table) { table(:users) } + + subject { described_class.new } + + describe '#perform' do + context 'with group members' do + let(:user_1) { create_user('user@example.com') } + let!(:member) { create_group_member(user_1, group_with_2fa_parent) } + let!(:user_without_group) { create_user('user_without@example.com') } + let(:user_other) { create_user('user_other@example.com') } + let!(:member_other) { create_group_member(user_other, group_with_2fa_parent) } + + it 'updates user when user should be required to establish two factor authentication' do + subject.perform(user_1.id, user_without_group.id) + + expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true) + end + + it 'does not update user who is not in current batch' do + subject.perform(user_1.id, user_without_group.id) + + expect(user_other.reload.require_two_factor_authentication_from_group).to eq(false) + end + + it 'updates all users in current batch' do + subject.perform(user_1.id, user_other.id) + + expect(user_other.reload.require_two_factor_authentication_from_group).to eq(true) + end + + it 'updates user when user is member of group in which parent group requires two factor authentication' do + member.destroy! + + subgroup = create_namespace('subgroup', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: false, parent_id: group_with_2fa_child.id) + create_group_member(user_1, subgroup) + + subject.perform(user_1.id, user_other.id) + + expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true) + end + + it 'updates user when user is member of a group and the subgroup requires two factor authentication' do + member.destroy! + + parent = create_namespace('other_parent', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: false) + create_namespace('other_subgroup', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true, parent_id: parent.id) + create_group_member(user_1, parent) + + subject.perform(user_1.id, user_other.id) + + expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true) + end + + it 'does not update user when not a member of a group that requires two factor authentication' do + member_other.destroy! + + other_group = create_namespace('other_group', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: false) + create_group_member(user_other, other_group) + + subject.perform(user_1.id, user_other.id) + + expect(user_other.reload.require_two_factor_authentication_from_group).to eq(false) + end + end + end + + def create_user(email, require_2fa: false) + users_table.create!(email: email, projects_limit: 10, require_two_factor_authentication_from_group: require_2fa) + end + + def create_group_member(user, group) + members_table.create!(user_id: user.id, source_id: group.id, access_level: GroupMember::MAINTAINER, source_type: "Namespace", type: "GroupMember", notification_level: 3) + end +end diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb index 175b12637e6..ad89f1f5cda 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb @@ -128,7 +128,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do context 'when environment has already been created' do before do - create(:environment, :staging, project: project, name: 'customer-portal') + create(:environment, project: project, name: 'customer-portal', tier: :staging) end it 'does not overwrite the specified deployment tier' do diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index ae9c697e0b9..3d6c04fd484 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -433,6 +433,13 @@ RSpec.describe Gitlab::GitAccess do expect { pull_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}") end + it 'disallows users with expired password to pull' do + project.add_maintainer(user) + user.update!(password_expires_at: 2.minutes.ago) + + expect { pull_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.") + end + context 'when the project repository does not exist' do before do project.add_guest(user) @@ -969,6 +976,13 @@ RSpec.describe Gitlab::GitAccess do expect { push_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}") end + it 'disallows users with expired password to push' do + project.add_maintainer(user) + user.update!(password_expires_at: 2.minutes.ago) + + expect { push_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.") + end + it 'cleans up the files' do expect(project.repository).to receive(:clean_stale_repository_files).and_call_original expect { push_access_check }.not_to raise_error diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index c7e77a34582..dc7fedd6b4f 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -624,6 +624,7 @@ metrics_setting: - project protected_environments: - project +- group - deploy_access_levels deploy_access_levels: - protected_environment diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 0be1b09851f..2173bee6b4b 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -701,6 +701,7 @@ ProjectSetting: ProtectedEnvironment: - id - project_id +- group_id - name - created_at - updated_at diff --git a/spec/lib/gitlab/utils/nokogiri_spec.rb b/spec/lib/gitlab/utils/nokogiri_spec.rb new file mode 100644 index 00000000000..90f137f53c8 --- /dev/null +++ b/spec/lib/gitlab/utils/nokogiri_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Utils::Nokogiri do + describe '#css_to_xpath' do + using RSpec::Parameterized::TableSyntax + + where(:css, :xpath) do + 'img' | "descendant-or-self::img" + 'a.gfm' | "descendant-or-self::a[contains(concat(' ',normalize-space(@class),' '),' gfm ')]" + 'a:not(.gfm)' | "descendant-or-self::a[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]" + 'video, audio' | "descendant-or-self::video|descendant-or-self::audio" + '[data-math-style]' | "descendant-or-self::*[@data-math-style]" + '[data-mermaid-style]' | "descendant-or-self::*[@data-mermaid-style]" + '.js-render-metrics' | "descendant-or-self::*[contains(concat(' ',normalize-space(@class),' '),' js-render-metrics ')]" + 'h1, h2, h3, h4, h5, h6' | "descendant-or-self::h1|descendant-or-self::h2|descendant-or-self::h3|descendant-or-self::h4|descendant-or-self::h5|descendant-or-self::h6" + 'pre.code.language-math' | "descendant-or-self::pre[contains(concat(' ',normalize-space(@class),' '),' code ') and contains(concat(' ',normalize-space(@class),' '),' language-math ')]" + 'pre > code[lang="plantuml"]' | "descendant-or-self::pre/code[@lang=\"plantuml\"]" + 'pre[lang="mermaid"] > code' | "descendant-or-self::pre[@lang=\"mermaid\"]/code" + 'pre.language-suggestion' | "descendant-or-self::pre[contains(concat(' ',normalize-space(@class),' '),' language-suggestion ')]" + 'pre.language-suggestion > code' | "descendant-or-self::pre[contains(concat(' ',normalize-space(@class),' '),' language-suggestion ')]/code" + 'a.gfm[data-reference-type="user"]' | "descendant-or-self::a[contains(concat(' ',normalize-space(@class),' '),' gfm ') and @data-reference-type=\"user\"]" + 'a:not(.gfm), img:not(.gfm), video:not(.gfm), audio:not(.gfm)' | "descendant-or-self::a[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]|descendant-or-self::img[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]|descendant-or-self::video[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]|descendant-or-self::audio[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]" + 'pre:not([data-math-style]):not([data-mermaid-style]):not([data-kroki-style]) > code' | "descendant-or-self::pre[not(@data-math-style) and not(@data-mermaid-style) and not(@data-kroki-style)]/code" + end + + with_them do + it 'generates the xpath' do + expect(described_class.css_to_xpath(css)).to eq xpath + end + end + end +end diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb index 11dba610faf..a7ccce0aaab 100644 --- a/spec/lib/gitlab/utils_spec.rb +++ b/spec/lib/gitlab/utils_spec.rb @@ -417,6 +417,29 @@ RSpec.describe Gitlab::Utils do end end + describe '.removes_sensitive_data_from_url' do + it 'returns string object' do + expect(described_class.removes_sensitive_data_from_url('http://gitlab.com')).to be_instance_of(String) + end + + it 'returns nil when URI cannot be parsed' do + expect(described_class.removes_sensitive_data_from_url('://gitlab.com')).to be nil + end + + it 'returns nil with invalid parameter' do + expect(described_class.removes_sensitive_data_from_url(1)).to be nil + end + + it 'returns string with filtered access_token param' do + expect(described_class.removes_sensitive_data_from_url('http://gitlab.com/auth.html#access_token=secret_token')).to eq('http://gitlab.com/auth.html#access_token=filtered') + end + + it 'returns string with filtered access_token param but other params preserved' do + expect(described_class.removes_sensitive_data_from_url('http://gitlab.com/auth.html#access_token=secret_token&token_type=Bearer&state=test')) + .to include('&token_type=Bearer', '&state=test') + end + end + describe 'multiple_key_invert' do it 'invert keys with array values' do hash = { diff --git a/spec/lib/gitlab/x509/signature_spec.rb b/spec/lib/gitlab/x509/signature_spec.rb index 2ac9c1f3a3b..7ba15faf910 100644 --- a/spec/lib/gitlab/x509/signature_spec.rb +++ b/spec/lib/gitlab/x509/signature_spec.rb @@ -12,20 +12,30 @@ RSpec.describe Gitlab::X509::Signature do end shared_examples "a verified signature" do - it 'returns a verified signature if email does match' do - signature = described_class.new( + let_it_be(:user) { create(:user, email: X509Helpers::User1.certificate_email) } + + subject(:signature) do + described_class.new( X509Helpers::User1.signed_commit_signature, X509Helpers::User1.signed_commit_base_data, X509Helpers::User1.certificate_email, X509Helpers::User1.signed_commit_time ) + end + it 'returns a verified signature if email does match' do expect(signature.x509_certificate).to have_attributes(certificate_attributes) expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) expect(signature.verified_signature).to be_truthy expect(signature.verification_status).to eq(:verified) end + it "returns an unverified signature if the email matches but isn't confirmed" do + user.update!(confirmed_at: nil) + + expect(signature.verification_status).to eq(:unverified) + end + it 'returns an unverified signature if email does not match' do signature = described_class.new( X509Helpers::User1.signed_commit_signature, @@ -55,13 +65,6 @@ RSpec.describe Gitlab::X509::Signature do end it 'returns an unverified signature if certificate is revoked' do - signature = described_class.new( - X509Helpers::User1.signed_commit_signature, - X509Helpers::User1.signed_commit_base_data, - X509Helpers::User1.certificate_email, - X509Helpers::User1.signed_commit_time - ) - expect(signature.verification_status).to eq(:verified) signature.x509_certificate.revoked! @@ -253,23 +256,25 @@ RSpec.describe Gitlab::X509::Signature do end describe '#user' do - signature = described_class.new( - X509Helpers::User1.signed_tag_signature, - X509Helpers::User1.signed_tag_base_data, - X509Helpers::User1.certificate_email, - X509Helpers::User1.signed_commit_time - ) + subject do + described_class.new( + X509Helpers::User1.signed_tag_signature, + X509Helpers::User1.signed_tag_base_data, + X509Helpers::User1.certificate_email, + X509Helpers::User1.signed_commit_time + ).user + end context 'if email is assigned to a user' do let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) } it 'returns user' do - expect(signature.user).to eq(user) + is_expected.to eq(user) end end it 'if email is not assigned to a user, return nil' do - expect(signature.user).to be_nil + is_expected.to be_nil end end @@ -292,6 +297,17 @@ RSpec.describe Gitlab::X509::Signature do end context 'verified signature' do + let_it_be(:user) { create(:user, email: X509Helpers::User1.certificate_email) } + + subject(:signature) do + described_class.new( + X509Helpers::User1.signed_tag_signature, + X509Helpers::User1.signed_tag_base_data, + X509Helpers::User1.certificate_email, + X509Helpers::User1.signed_commit_time + ) + end + context 'with trusted certificate store' do before do store = OpenSSL::X509::Store.new @@ -301,19 +317,18 @@ RSpec.describe Gitlab::X509::Signature do end it 'returns a verified signature if email does match' do - signature = described_class.new( - X509Helpers::User1.signed_tag_signature, - X509Helpers::User1.signed_tag_base_data, - X509Helpers::User1.certificate_email, - X509Helpers::User1.signed_commit_time - ) - expect(signature.x509_certificate).to have_attributes(certificate_attributes) expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) expect(signature.verified_signature).to be_truthy expect(signature.verification_status).to eq(:verified) end + it "returns an unverified signature if the email matches but isn't confirmed" do + user.update!(confirmed_at: nil) + + expect(signature.verification_status).to eq(:unverified) + end + it 'returns an unverified signature if email does not match' do signature = described_class.new( X509Helpers::User1.signed_tag_signature, @@ -343,13 +358,6 @@ RSpec.describe Gitlab::X509::Signature do end it 'returns an unverified signature if certificate is revoked' do - signature = described_class.new( - X509Helpers::User1.signed_tag_signature, - X509Helpers::User1.signed_tag_base_data, - X509Helpers::User1.certificate_email, - X509Helpers::User1.signed_commit_time - ) - expect(signature.verification_status).to eq(:verified) signature.x509_certificate.revoked! @@ -368,13 +376,6 @@ RSpec.describe Gitlab::X509::Signature do end it 'returns an unverified signature' do - signature = described_class.new( - X509Helpers::User1.signed_tag_signature, - X509Helpers::User1.signed_tag_base_data, - X509Helpers::User1.certificate_email, - X509Helpers::User1.signed_commit_time - ) - expect(signature.x509_certificate).to have_attributes(certificate_attributes) expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) expect(signature.verified_signature).to be_falsey diff --git a/spec/migrations/20210601080039_group_protected_environments_add_index_and_constraint_spec.rb b/spec/migrations/20210601080039_group_protected_environments_add_index_and_constraint_spec.rb new file mode 100644 index 00000000000..d3154596b26 --- /dev/null +++ b/spec/migrations/20210601080039_group_protected_environments_add_index_and_constraint_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration!('group_protected_environments_add_index_and_constraint') + +RSpec.describe GroupProtectedEnvironmentsAddIndexAndConstraint do + let(:migration) { described_class.new } + let(:protected_environments) { table(:protected_environments) } + let(:group) { table(:namespaces).create!(name: 'group', path: 'group') } + let(:project) { table(:projects).create!(name: 'project', path: 'project', namespace_id: group.id) } + + describe '#down' do + it 'deletes only group-level configurations' do + migration.up + + project_protections = [ + protected_environments.create!(project_id: project.id, name: 'production'), + protected_environments.create!(project_id: project.id, name: 'staging') + ] + protected_environments.create!(group_id: group.id, name: 'production') + protected_environments.create!(group_id: group.id, name: 'staging') + + migration.down + + expect(protected_environments.pluck(:id)) + .to match_array project_protections.map(&:id) + end + end +end diff --git a/spec/migrations/backfill_escalation_policies_for_oncall_schedules_spec.rb b/spec/migrations/backfill_escalation_policies_for_oncall_schedules_spec.rb new file mode 100644 index 00000000000..86415b1520e --- /dev/null +++ b/spec/migrations/backfill_escalation_policies_for_oncall_schedules_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20210519220019_backfill_escalation_policies_for_oncall_schedules.rb') + +RSpec.describe BackfillEscalationPoliciesForOncallSchedules do + let_it_be(:projects) { table(:projects) } + let_it_be(:schedules) { table(:incident_management_oncall_schedules) } + let_it_be(:policies) { table(:incident_management_escalation_policies) } + let_it_be(:rules) { table(:incident_management_escalation_rules) } + + # Project with no schedules + let_it_be(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab') } + let_it_be(:project_a) { projects.create!(namespace_id: namespace.id) } + + context 'with backfill-able schedules' do + # Project with one schedule + let_it_be(:project_b) { projects.create!(namespace_id: namespace.id) } + let_it_be(:schedule_b1) { schedules.create!(project_id: project_b.id, iid: 1, name: 'Schedule B1') } + + # Project with multiple schedules + let_it_be(:project_c) { projects.create!(namespace_id: namespace.id) } + let_it_be(:schedule_c1) { schedules.create!(project_id: project_c.id, iid: 1, name: 'Schedule C1') } + let_it_be(:schedule_c2) { schedules.create!(project_id: project_c.id, iid: 2, name: 'Schedule C2') } + + # Project with a single schedule which already has a policy + let_it_be(:project_d) { projects.create!(namespace_id: namespace.id) } + let_it_be(:schedule_d1) { schedules.create!(project_id: project_d.id, iid: 1, name: 'Schedule D1') } + let_it_be(:policy_d1) { policies.create!(project_id: project_d.id, name: 'Policy D1') } + let_it_be(:rule_d1) { rules.create!(policy_id: policy_d1.id, oncall_schedule_id: schedule_d1.id, status: 2, elapsed_time_seconds: 60) } + + # Project with a multiple schedule, one of which already has a policy + let_it_be(:project_e) { projects.create!(namespace_id: namespace.id) } + let_it_be(:schedule_e1) { schedules.create!(project_id: project_e.id, iid: 1, name: 'Schedule E1') } + let_it_be(:schedule_e2) { schedules.create!(project_id: project_e.id, iid: 2, name: 'Schedule E2') } + let_it_be(:policy_e1) { policies.create!(project_id: project_e.id, name: 'Policy E1') } + let_it_be(:rule_e1) { rules.create!(policy_id: policy_e1.id, oncall_schedule_id: schedule_e2.id, status: 2, elapsed_time_seconds: 60) } + + # Project with a multiple schedule, with multiple policies + let_it_be(:project_f) { projects.create!(namespace_id: namespace.id) } + let_it_be(:schedule_f1) { schedules.create!(project_id: project_f.id, iid: 1, name: 'Schedule F1') } + let_it_be(:schedule_f2) { schedules.create!(project_id: project_f.id, iid: 2, name: 'Schedule F2') } + let_it_be(:policy_f1) { policies.create!(project_id: project_f.id, name: 'Policy F1') } + let_it_be(:rule_f1) { rules.create!(policy_id: policy_f1.id, oncall_schedule_id: schedule_f1.id, status: 2, elapsed_time_seconds: 60) } + let_it_be(:rule_f2) { rules.create!(policy_id: policy_f1.id, oncall_schedule_id: schedule_f2.id, status: 2, elapsed_time_seconds: 60) } + let_it_be(:policy_f2) { policies.create!(project_id: project_f.id, name: 'Policy F2') } + let_it_be(:rule_f3) { rules.create!(policy_id: policy_f2.id, oncall_schedule_id: schedule_f2.id, status: 1, elapsed_time_seconds: 10) } + + it 'backfills escalation policies correctly' do + expect { migrate! } + .to change(policies, :count).by(2) + .and change(rules, :count).by(3) + + new_policy_b1, new_policy_c1 = new_polices = policies.last(2) + new_rules = rules.last(3) + + expect(new_polices).to all have_attributes(name: 'On-call Escalation Policy') + expect(new_policy_b1.description).to eq('Immediately notify Schedule B1') + expect(new_policy_c1.description).to eq('Immediately notify Schedule C1') + expect(policies.pluck(:project_id)).to eq([ + project_d.id, + project_e.id, + project_f.id, + project_f.id, + project_b.id, + project_c.id + ]) + + expect(new_rules).to all have_attributes(status: 1, elapsed_time_seconds: 0) + expect(rules.pluck(:policy_id)).to eq([ + rule_d1.policy_id, + rule_e1.policy_id, + rule_f1.policy_id, + rule_f2.policy_id, + rule_f3.policy_id, + new_policy_b1.id, + new_policy_c1.id, + new_policy_c1.id + ]) + expect(rules.pluck(:oncall_schedule_id)).to eq([ + rule_d1.oncall_schedule_id, + rule_e1.oncall_schedule_id, + rule_f1.oncall_schedule_id, + rule_f2.oncall_schedule_id, + rule_f3.oncall_schedule_id, + schedule_b1.id, + schedule_c1.id, + schedule_c2.id + ]) + end + end + + context 'with no schedules' do + it 'does nothing' do + expect { migrate! } + .to not_change(policies, :count) + .and not_change(rules, :count) + end + end +end diff --git a/spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb b/spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb new file mode 100644 index 00000000000..cec141cacc9 --- /dev/null +++ b/spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb') + +RSpec.describe ScheduleUpdateUsersWhereTwoFactorAuthRequiredFromGroup do + let(:users) { table(:users) } + let!(:user_1) { users.create!(require_two_factor_authentication_from_group: false, name: "user1", email: "user1@example.com", projects_limit: 1) } + let!(:user_2) { users.create!(require_two_factor_authentication_from_group: true, name: "user2", email: "user2@example.com", projects_limit: 1) } + let!(:user_3) { users.create!(require_two_factor_authentication_from_group: false, name: "user3", email: "user3@example.com", projects_limit: 1) } + + before do + stub_const("#{described_class.name}::BATCH_SIZE", 1) + end + + it 'schedules jobs for users that do not require two factor authentication' do + Sidekiq::Testing.fake! do + freeze_time do + migrate! + + expect(described_class::MIGRATION).to be_scheduled_delayed_migration( + 2.minutes, user_1.id, user_1.id) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration( + 4.minutes, user_3.id, user_3.id) + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end +end diff --git a/spec/models/concerns/has_timelogs_report_spec.rb b/spec/models/concerns/has_timelogs_report_spec.rb deleted file mode 100644 index f0dca47fae1..00000000000 --- a/spec/models/concerns/has_timelogs_report_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe HasTimelogsReport do - let_it_be(:user) { create(:user) } - - let(:group) { create(:group) } - let(:project) { create(:project, :public, group: group) } - let(:issue1) { create(:issue, project: project) } - let(:merge_request1) { create(:merge_request, source_project: project) } - - describe '#timelogs' do - let_it_be(:start_time) { 20.days.ago } - let_it_be(:end_time) { 8.days.ago } - - let!(:timelog1) { create_timelog(15.days.ago, issue: issue1) } - let!(:timelog2) { create_timelog(10.days.ago, merge_request: merge_request1) } - let!(:timelog3) { create_timelog(5.days.ago, issue: issue1) } - - before do - group.add_developer(user) - end - - it 'returns collection of timelogs between given times' do - expect(group.timelogs(start_time, end_time).to_a).to match_array([timelog1, timelog2]) - end - - it 'returns empty collection if times are not present' do - expect(group.timelogs(nil, nil)).to be_empty - end - - it 'returns empty collection if time range is invalid' do - expect(group.timelogs(end_time, start_time)).to be_empty - end - end - - describe '#user_can_access_group_timelogs?' do - it 'returns true if user can access group timelogs' do - group.add_developer(user) - - expect(group).to be_user_can_access_group_timelogs(user) - end - - it 'returns false if user has insufficient permissions' do - group.add_guest(user) - - expect(group).not_to be_user_can_access_group_timelogs(user) - end - end - - def create_timelog(time, issue: nil, merge_request: nil) - create(:timelog, issue: issue, merge_request: merge_request, user: user, spent_at: time) - end -end diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb index c3432907112..bc042f7a639 100644 --- a/spec/models/timelog_spec.rb +++ b/spec/models/timelog_spec.rb @@ -64,25 +64,57 @@ RSpec.describe Timelog do let_it_be(:subgroup_issue) { create(:issue, project: subgroup_project) } let_it_be(:subgroup_merge_request) { create(:merge_request, source_project: subgroup_project) } - let_it_be(:timelog) { create(:issue_timelog, spent_at: 65.days.ago) } - let_it_be(:timelog1) { create(:issue_timelog, spent_at: 15.days.ago, issue: group_issue) } - let_it_be(:timelog2) { create(:issue_timelog, spent_at: 5.days.ago, issue: subgroup_issue) } - let_it_be(:timelog3) { create(:merge_request_timelog, spent_at: 65.days.ago) } - let_it_be(:timelog4) { create(:merge_request_timelog, spent_at: 15.days.ago, merge_request: group_merge_request) } - let_it_be(:timelog5) { create(:merge_request_timelog, spent_at: 5.days.ago, merge_request: subgroup_merge_request) } - - describe 'in_group' do + let_it_be(:short_time_ago) { 5.days.ago } + let_it_be(:medium_time_ago) { 15.days.ago } + let_it_be(:long_time_ago) { 65.days.ago } + + let_it_be(:timelog) { create(:issue_timelog, spent_at: long_time_ago) } + let_it_be(:timelog1) { create(:issue_timelog, spent_at: medium_time_ago, issue: group_issue) } + let_it_be(:timelog2) { create(:issue_timelog, spent_at: short_time_ago, issue: subgroup_issue) } + let_it_be(:timelog3) { create(:merge_request_timelog, spent_at: long_time_ago) } + let_it_be(:timelog4) { create(:merge_request_timelog, spent_at: medium_time_ago, merge_request: group_merge_request) } + let_it_be(:timelog5) { create(:merge_request_timelog, spent_at: short_time_ago, merge_request: subgroup_merge_request) } + + describe '.in_group' do it 'return timelogs created for group issues and merge requests' do expect(described_class.in_group(group)).to contain_exactly(timelog1, timelog2, timelog4, timelog5) end end - describe 'between_times' do - it 'returns collection of timelogs within given times' do - timelogs = described_class.between_times(20.days.ago, 10.days.ago) + describe '.at_or_after' do + it 'returns timelogs at the time limit' do + timelogs = described_class.at_or_after(short_time_ago) - expect(timelogs).to contain_exactly(timelog1, timelog4) + expect(timelogs).to contain_exactly(timelog2, timelog5) end + + it 'returns timelogs after given time' do + timelogs = described_class.at_or_after(just_before(short_time_ago)) + + expect(timelogs).to contain_exactly(timelog2, timelog5) + end + end + + describe '.at_or_before' do + it 'returns timelogs at the time limit' do + timelogs = described_class.at_or_before(long_time_ago) + + expect(timelogs).to contain_exactly(timelog, timelog3) + end + + it 'returns timelogs before given time' do + timelogs = described_class.at_or_before(just_after(long_time_ago)) + + expect(timelogs).to contain_exactly(timelog, timelog3) + end + end + + def just_before(time) + time - 1.day + end + + def just_after(time) + time + 1.day end end end diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index e677f5558fd..bbbc5d08c07 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -239,6 +239,14 @@ RSpec.describe GlobalPolicy do it { is_expected.not_to be_allowed(:access_api) } end + context 'user with expired password' do + before do + current_user.update!(password_expires_at: 2.minutes.ago) + end + + it { is_expected.not_to be_allowed(:access_api) } + end + context 'when terms are enforced' do before do enforce_terms @@ -418,6 +426,14 @@ RSpec.describe GlobalPolicy do it { is_expected.not_to be_allowed(:access_git) } end + + context 'user with expired password' do + before do + current_user.update!(password_expires_at: 2.minutes.ago) + end + + it { is_expected.not_to be_allowed(:access_git) } + end end describe 'read instance metadata' do @@ -494,6 +510,14 @@ RSpec.describe GlobalPolicy do it { is_expected.not_to be_allowed(:use_slash_commands) } end + + context 'user with expired password' do + before do + current_user.update!(password_expires_at: 2.minutes.ago) + end + + it { is_expected.not_to be_allowed(:use_slash_commands) } + end end describe 'create_snippet' do diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index f5e389ff338..ee87a2da189 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -923,54 +923,4 @@ RSpec.describe GroupPolicy do it { expect(described_class.new(current_user, subgroup)).to be_allowed(:read_label) } end end - - context 'timelogs' do - context 'with admin' do - let(:current_user) { admin } - - context 'when admin mode is enabled', :enable_admin_mode do - it { is_expected.to be_allowed(:read_group_timelogs) } - end - - context 'when admin mode is disabled' do - it { is_expected.to be_disallowed(:read_group_timelogs) } - end - end - - context 'with owner' do - let(:current_user) { owner } - - it { is_expected.to be_allowed(:read_group_timelogs) } - end - - context 'with maintainer' do - let(:current_user) { maintainer } - - it { is_expected.to be_allowed(:read_group_timelogs) } - end - - context 'with reporter' do - let(:current_user) { reporter } - - it { is_expected.to be_allowed(:read_group_timelogs) } - end - - context 'with guest' do - let(:current_user) { guest } - - it { is_expected.to be_disallowed(:read_group_timelogs) } - end - - context 'with non member' do - let(:current_user) { create(:user) } - - it { is_expected.to be_disallowed(:read_group_timelogs) } - end - - context 'with anonymous' do - let(:current_user) { nil } - - it { is_expected.to be_disallowed(:read_group_timelogs) } - end - end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 46da42a4787..d0abcfbd091 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1385,54 +1385,4 @@ RSpec.describe ProjectPolicy do end end end - - context 'timelogs' do - context 'with admin' do - let(:current_user) { admin } - - context 'when admin mode enabled', :enable_admin_mode do - it { is_expected.to be_allowed(:read_group_timelogs) } - end - - context 'when admin mode disabled' do - it { is_expected.to be_disallowed(:read_group_timelogs) } - end - end - - context 'with owner' do - let(:current_user) { owner } - - it { is_expected.to be_allowed(:read_group_timelogs) } - end - - context 'with maintainer' do - let(:current_user) { maintainer } - - it { is_expected.to be_allowed(:read_group_timelogs) } - end - - context 'with reporter' do - let(:current_user) { reporter } - - it { is_expected.to be_allowed(:read_group_timelogs) } - end - - context 'with guest' do - let(:current_user) { guest } - - it { is_expected.to be_disallowed(:read_group_timelogs) } - end - - context 'with non member' do - let(:current_user) { non_member } - - it { is_expected.to be_disallowed(:read_group_timelogs) } - end - - context 'with anonymous' do - let(:current_user) { anonymous } - - it { is_expected.to be_disallowed(:read_group_timelogs) } - end - end end diff --git a/spec/requests/api/graphql/group/timelogs_spec.rb b/spec/requests/api/graphql/group/timelogs_spec.rb index 6e21a73afa9..05b6ee3ff89 100644 --- a/spec/requests/api/graphql/group/timelogs_spec.rb +++ b/spec/requests/api/graphql/group/timelogs_spec.rb @@ -17,8 +17,37 @@ RSpec.describe 'Timelogs through GroupQuery' do let(:timelogs_data) { graphql_data['group']['timelogs']['nodes'] } - before do - group.add_developer(user) + context 'when the project is private' do + let_it_be(:group2) { create(:group) } + let_it_be(:project2) { create(:project, :private, group: group2) } + let_it_be(:issue2) { create(:issue, project: project2) } + let_it_be(:timelog3) { create(:timelog, issue: issue2, spent_at: '2019-08-13 14:00:00') } + + subject { post_graphql(query(full_path: group2.full_path), current_user: user) } + + context 'when the user is not a member of the project' do + it 'returns no timelogs' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(graphql_errors).to be_nil + expect(timelog_array.size).to eq 0 + end + end + + context 'when the user is a member of the project' do + before do + project2.add_developer(user) + end + + it 'returns timelogs' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(graphql_errors).to be_nil + expect(timelog_array.size).to eq 1 + end + end end context 'when the request is correct' do @@ -74,18 +103,6 @@ RSpec.describe 'Timelogs through GroupQuery' do expect(timelogs_data).to be_empty end end - - context 'when user has no permission to read group timelogs' do - it 'returns empty result' do - guest = create(:user) - group.add_guest(guest) - post_graphql(query, current_user: guest) - - expect(response).to have_gitlab_http_status(:success) - expect(graphql_errors).to be_nil - expect(timelogs_data).to be_empty - end - end end end @@ -95,7 +112,7 @@ RSpec.describe 'Timelogs through GroupQuery' do end end - def query(timelog_params = params) + def query(timelog_params: params, full_path: group.full_path) timelog_nodes = <<~NODE nodes { spentAt @@ -114,7 +131,7 @@ RSpec.describe 'Timelogs through GroupQuery' do graphql_query_for( :group, - { full_path: group.full_path }, + { full_path: full_path }, query_graphql_field(:timelogs, timelog_params, timelog_nodes) ) end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index a1e28c18769..279c65fc2f4 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -35,6 +35,26 @@ RSpec.describe 'Git HTTP requests' do expect(response.header['WWW-Authenticate']).to start_with('Basic ') end end + + context "when password is expired" do + it "responds to downloads with status 401 Unauthorized" do + user.update!(password_expires_at: 2.days.ago) + + download(path, user: user.username, password: user.password) do |response| + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end + + context "when user is blocked" do + let(:user) { create(:user, :blocked) } + + it "responds to downloads with status 401 Unauthorized" do + download(path, user: user.username, password: user.password) do |response| + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end end context "when authentication succeeds" do @@ -75,6 +95,15 @@ RSpec.describe 'Git HTTP requests' do expect(response.header['WWW-Authenticate']).to start_with('Basic ') end end + + context "when password is expired" do + it "responds to uploads with status 401 Unauthorized" do + user.update!(password_expires_at: 2.days.ago) + upload(path, user: user.username, password: user.password) do |response| + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end end context "when authentication succeeds" do @@ -576,6 +605,16 @@ RSpec.describe 'Git HTTP requests' do it_behaves_like 'pulls are allowed' it_behaves_like 'pushes are allowed' + + context "when password is expired" do + it "responds to downloads with status 401 unauthorized" do + user.update!(password_expires_at: 2.days.ago) + + download(path, **env) do |response| + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end end context 'when user has 2FA enabled' do @@ -649,6 +688,18 @@ RSpec.describe 'Git HTTP requests' do expect(response).to have_gitlab_http_status(:ok) end end + + context "when password is expired" do + it "responds to uploads with status 401 unauthorized" do + user.update!(password_expires_at: 2.days.ago) + + write_access_token = create(:personal_access_token, user: user, scopes: [:write_repository]) + + upload(path, user: user.username, password: write_access_token.token) do |response| + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end end end @@ -860,6 +911,16 @@ RSpec.describe 'Git HTTP requests' do expect(response).to have_gitlab_http_status(:not_found) end + + context 'when users password is expired' do + it 'rejects pulls with 401 unauthorized' do + user.update!(password_expires_at: 2.days.ago) + + download(path, user: 'gitlab-ci-token', password: build.token) do |response| + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end end end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 4e18c9cb4ca..0e3a0252638 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -346,9 +346,7 @@ RSpec.describe 'Git LFS API and storage' do let_it_be(:user) { create(:user, password_expires_at: 1.minute.ago)} let(:role) { :reporter} - # TODO: This should return a 404 response - # https://gitlab.com/gitlab-org/gitlab/-/issues/292006 - it_behaves_like 'LFS http 200 response' + it_behaves_like 'LFS http 401 response' end context 'when user is blocked' do diff --git a/spec/tasks/gitlab/x509/update_rake_spec.rb b/spec/tasks/gitlab/x509/update_rake_spec.rb index 93e97ab38ad..b166e73935a 100644 --- a/spec/tasks/gitlab/x509/update_rake_spec.rb +++ b/spec/tasks/gitlab/x509/update_rake_spec.rb @@ -8,12 +8,13 @@ RSpec.describe 'gitlab:x509 namespace rake task' do end describe 'update_signatures' do - subject { run_rake_task('gitlab:x509:update_signatures') } - - let(:project) { create :project, :repository, path: X509Helpers::User1.path } + let(:user) { create(:user, email: X509Helpers::User1.certificate_email) } + let(:project) { create(:project, :repository, path: X509Helpers::User1.path, creator: user) } let(:x509_signed_commit) { project.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') } let(:x509_commit) { Gitlab::X509::Commit.new(x509_signed_commit).signature } + subject { run_rake_task('gitlab:x509:update_signatures') } + it 'changes from unverified to verified if the certificate store contains the root certificate' do x509_commit @@ -22,21 +23,14 @@ RSpec.describe 'gitlab:x509 namespace rake task' do store.add_cert(certificate) allow(OpenSSL::X509::Store).to receive(:new).and_return(store) - expect(x509_commit.verification_status).to eq('unverified') expect_any_instance_of(Gitlab::X509::Commit).to receive(:update_signature!).and_call_original - - subject - - x509_commit.reload - expect(x509_commit.verification_status).to eq('verified') + expect { subject }.to change { x509_commit.reload.verification_status }.from('unverified').to('verified') end it 'returns if no signature is available' do - expect_any_instance_of(Gitlab::X509::Commit) do |x509_commit| - expect(x509_commit).not_to receive(:update_signature!) + expect_any_instance_of(Gitlab::X509::Commit).not_to receive(:update_signature!) - subject - end + subject end end end |