From 15c040a6bd71894260b66a90685070c0babfee76 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 31 May 2021 11:42:18 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@13-12-stable-ee --- spec/features/users/login_spec.rb | 19 +++++++ spec/initializers/lograge_spec.rb | 21 ++++++++ .../banzai/pipeline/post_process_pipeline_spec.rb | 7 ++- .../gitlab/auth/user_access_denied_reason_spec.rb | 8 +++ spec/lib/gitlab/git_access_spec.rb | 14 +++++ spec/lib/gitlab/utils/nokogiri_spec.rb | 34 ++++++++++++ spec/lib/gitlab/utils_spec.rb | 23 ++++++++ spec/policies/global_policy_spec.rb | 24 +++++++++ spec/requests/git_http_spec.rb | 61 ++++++++++++++++++++++ spec/requests/lfs_http_spec.rb | 4 +- 10 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 spec/lib/gitlab/utils/nokogiri_spec.rb (limited to 'spec') 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/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/pipeline/post_process_pipeline_spec.rb b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb index ebe1ca4d403..55038d58f22 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 @@ -49,7 +51,8 @@ RSpec.describe Banzai::Pipeline::PostProcessPipeline do end it 'searches for attributes twice' do - expect(doc).to receive(:search).twice.and_call_original + expect(doc).to receive(:xpath).exactly(non_related_xpath_calls + 2).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/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/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/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/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 -- cgit v1.2.3