Commit 778c1567 authored by Michael Kozono's avatar Michael Kozono

Merge branch '332455-password-expired-error-on-git-fetch-via-ssh-for-ldap-user' into 'master'

Fix Password expired error on git fetch via SSH for LDAP user

See merge request gitlab-org/gitlab!63466
parents 8c9b0947 e521e973
...@@ -1869,6 +1869,12 @@ class User < ApplicationRecord ...@@ -1869,6 +1869,12 @@ class User < ApplicationRecord
!!(password_expires_at && password_expires_at < Time.current) !!(password_expires_at && password_expires_at < Time.current)
end end
def password_expired_if_applicable?
return false unless allow_password_authentication?
password_expired?
end
def can_be_deactivated? def can_be_deactivated?
active? && no_recent_activity? && !internal? active? && no_recent_activity? && !internal?
end end
......
...@@ -85,7 +85,7 @@ module PolicyActor ...@@ -85,7 +85,7 @@ module PolicyActor
false false
end end
def password_expired? def password_expired_if_applicable?
false false
end end
......
...@@ -16,7 +16,7 @@ class GlobalPolicy < BasePolicy ...@@ -16,7 +16,7 @@ class GlobalPolicy < BasePolicy
end end
condition(:password_expired, scope: :user) do condition(:password_expired, scope: :user) do
@user&.password_expired? @user&.password_expired_if_applicable?
end end
condition(:project_bot, scope: :user) { @user&.project_bot? } condition(:project_bot, scope: :user) { @user&.project_bot? }
......
...@@ -10,7 +10,7 @@ RSpec.describe API::Groups do ...@@ -10,7 +10,7 @@ RSpec.describe API::Groups do
let_it_be(:project) { create(:project, group: group) } let_it_be(:project) { create(:project, group: group) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:another_user) { create(:user) } let_it_be(:another_user) { create(:user) }
let(:admin) { create(:admin) } let_it_be(:admin) { create(:admin) }
before do before do
group.add_owner(user) group.add_owner(user)
......
...@@ -97,6 +97,8 @@ module API ...@@ -97,6 +97,8 @@ module API
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ":id/members" do post ":id/members" do
::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/333434')
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
authorize_admin_source!(source_type, source) authorize_admin_source!(source_type, source)
......
...@@ -385,7 +385,7 @@ module Gitlab ...@@ -385,7 +385,7 @@ module Gitlab
end end
def can_user_login_with_non_expired_password?(user) def can_user_login_with_non_expired_password?(user)
user.can?(:log_in) && !user.password_expired? user.can?(:log_in) && !user.password_expired_if_applicable?
end end
end end
end end
......
...@@ -23,6 +23,8 @@ module Gitlab ...@@ -23,6 +23,8 @@ module Gitlab
"Your primary email address is not confirmed. "\ "Your primary email address is not confirmed. "\
"Please check your inbox for the confirmation instructions. "\ "Please check your inbox for the confirmation instructions. "\
"In case the link is expired, you can request a new confirmation email at #{Rails.application.routes.url_helpers.new_user_confirmation_url}" "In case the link is expired, you can request a new confirmation email at #{Rails.application.routes.url_helpers.new_user_confirmation_url}"
when :blocked
"Your account has been blocked."
when :password_expired when :password_expired
"Your password expired. "\ "Your password expired. "\
"Please access GitLab from a web browser to update your password." "Please access GitLab from a web browser to update your password."
...@@ -44,6 +46,8 @@ module Gitlab ...@@ -44,6 +46,8 @@ module Gitlab
:deactivated :deactivated
elsif !@user.confirmed? elsif !@user.confirmed?
:unconfirmed :unconfirmed
elsif @user.blocked?
:blocked
elsif @user.password_expired? elsif @user.password_expired?
:password_expired :password_expired
else else
......
...@@ -52,7 +52,7 @@ module Gitlab ...@@ -52,7 +52,7 @@ module Gitlab
def valid_user? def valid_user?
return true unless user? return true unless user?
!actor.blocked? && (!actor.allow_password_authentication? || !actor.password_expired?) !actor.blocked? && !actor.password_expired_if_applicable?
end end
def authentication_payload(repository_http_path) def authentication_payload(repository_http_path)
......
...@@ -440,6 +440,14 @@ RSpec.describe Gitlab::GitAccess do ...@@ -440,6 +440,14 @@ RSpec.describe Gitlab::GitAccess do
expect { pull_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.") expect { pull_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
end end
it 'allows ldap users with expired password to pull' do
project.add_maintainer(user)
user.update!(password_expires_at: 2.minutes.ago)
allow(user).to receive(:ldap_user?).and_return(true)
expect { pull_access_check }.not_to raise_error
end
context 'when the project repository does not exist' do context 'when the project repository does not exist' do
before do before do
project.add_guest(user) project.add_guest(user)
...@@ -979,12 +987,26 @@ RSpec.describe Gitlab::GitAccess do ...@@ -979,12 +987,26 @@ RSpec.describe Gitlab::GitAccess do
end end
it 'disallows users with expired password to push' do it 'disallows users with expired password to push' do
project.add_maintainer(user)
user.update!(password_expires_at: 2.minutes.ago) 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.") expect { push_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
end end
it 'allows ldap users with expired password to push' do
user.update!(password_expires_at: 2.minutes.ago)
allow(user).to receive(:ldap_user?).and_return(true)
expect { push_access_check }.not_to raise_error
end
it 'disallows blocked ldap users with expired password to push' do
user.block
user.update!(password_expires_at: 2.minutes.ago)
allow(user).to receive(:ldap_user?).and_return(true)
expect { push_access_check }.to raise_forbidden("Your account has been blocked.")
end
it 'cleans up the files' do it 'cleans up the files' do
expect(project.repository).to receive(:clean_stale_repository_files).and_call_original expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
expect { push_access_check }.not_to raise_error expect { push_access_check }.not_to raise_error
......
...@@ -5224,6 +5224,70 @@ RSpec.describe User do ...@@ -5224,6 +5224,70 @@ RSpec.describe User do
end end
end end
describe '#password_expired_if_applicable?' do
let(:user) { build(:user, password_expires_at: password_expires_at) }
subject { user.password_expired_if_applicable? }
context 'when user is not ldap user' do
context 'when password_expires_at is not set' do
let(:password_expires_at) {}
it 'returns false' do
is_expected.to be_falsey
end
end
context 'when password_expires_at is in the past' do
let(:password_expires_at) { 1.minute.ago }
it 'returns true' do
is_expected.to be_truthy
end
end
context 'when password_expires_at is in the future' do
let(:password_expires_at) { 1.minute.from_now }
it 'returns false' do
is_expected.to be_falsey
end
end
end
context 'when user is ldap user' do
let(:user) { build(:user, password_expires_at: password_expires_at) }
before do
allow(user).to receive(:ldap_user?).and_return(true)
end
context 'when password_expires_at is not set' do
let(:password_expires_at) {}
it 'returns false' do
is_expected.to be_falsey
end
end
context 'when password_expires_at is in the past' do
let(:password_expires_at) { 1.minute.ago }
it 'returns false' do
is_expected.to be_falsey
end
end
context 'when password_expires_at is in the future' do
let(:password_expires_at) { 1.minute.from_now }
it 'returns false' do
is_expected.to be_falsey
end
end
end
end
describe '#read_only_attribute?' do describe '#read_only_attribute?' do
context 'when synced attributes metadata is present' do context 'when synced attributes metadata is present' do
it 'delegates to synced_attributes_metadata' do it 'delegates to synced_attributes_metadata' do
......
...@@ -245,6 +245,14 @@ RSpec.describe GlobalPolicy do ...@@ -245,6 +245,14 @@ RSpec.describe GlobalPolicy do
end end
it { is_expected.not_to be_allowed(:access_api) } it { is_expected.not_to be_allowed(:access_api) }
context 'when user is using ldap' do
before do
allow(current_user).to receive(:ldap_user?).and_return(true)
end
it { is_expected.to be_allowed(:access_api) }
end
end end
context 'when terms are enforced' do context 'when terms are enforced' do
...@@ -433,6 +441,14 @@ RSpec.describe GlobalPolicy do ...@@ -433,6 +441,14 @@ RSpec.describe GlobalPolicy do
end end
it { is_expected.not_to be_allowed(:access_git) } it { is_expected.not_to be_allowed(:access_git) }
context 'when user is using ldap' do
before do
allow(current_user).to receive(:ldap_user?).and_return(true)
end
it { is_expected.to be_allowed(:access_git) }
end
end end
end end
...@@ -517,6 +533,14 @@ RSpec.describe GlobalPolicy do ...@@ -517,6 +533,14 @@ RSpec.describe GlobalPolicy do
end end
it { is_expected.not_to be_allowed(:use_slash_commands) } it { is_expected.not_to be_allowed(:use_slash_commands) }
context 'when user is using ldap' do
before do
allow(current_user).to receive(:ldap_user?).and_return(true)
end
it { is_expected.to be_allowed(:use_slash_commands) }
end
end end
end end
......
...@@ -68,7 +68,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do ...@@ -68,7 +68,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do
context 'when the current user does not have permission to add assignees' do context 'when the current user does not have permission to add assignees' do
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
let(:db_query_limit) { 27 } let(:db_query_limit) { 28 }
it 'does not change the assignees' do it 'does not change the assignees' do
project.add_guest(current_user) project.add_guest(current_user)
...@@ -80,7 +80,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do ...@@ -80,7 +80,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do
end end
context 'with assignees already assigned' do context 'with assignees already assigned' do
let(:db_query_limit) { 39 } let(:db_query_limit) { 46 }
before do before do
merge_request.assignees = [assignee2] merge_request.assignees = [assignee2]
...@@ -96,7 +96,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do ...@@ -96,7 +96,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do
end end
context 'when passing an empty list of assignees' do context 'when passing an empty list of assignees' do
let(:db_query_limit) { 31 } let(:db_query_limit) { 32 }
let(:input) { { assignee_usernames: [] } } let(:input) { { assignee_usernames: [] } }
before do before do
...@@ -115,7 +115,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do ...@@ -115,7 +115,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do
context 'when passing append as true' do context 'when passing append as true' do
let(:mode) { Types::MutationOperationModeEnum.enum[:append] } let(:mode) { Types::MutationOperationModeEnum.enum[:append] }
let(:input) { { assignee_usernames: [assignee2.username], operation_mode: mode } } let(:input) { { assignee_usernames: [assignee2.username], operation_mode: mode } }
let(:db_query_limit) { 20 } let(:db_query_limit) { 22 }
before do before do
# In CE, APPEND is a NOOP as you can't have multiple assignees # In CE, APPEND is a NOOP as you can't have multiple assignees
...@@ -135,7 +135,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do ...@@ -135,7 +135,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do
end end
context 'when passing remove as true' do context 'when passing remove as true' do
let(:db_query_limit) { 31 } let(:db_query_limit) { 32 }
let(:mode) { Types::MutationOperationModeEnum.enum[:remove] } let(:mode) { Types::MutationOperationModeEnum.enum[:remove] }
let(:input) { { assignee_usernames: [assignee.username], operation_mode: mode } } let(:input) { { assignee_usernames: [assignee.username], operation_mode: mode } }
let(:expected_result) { [] } let(:expected_result) { [] }
......
...@@ -4,7 +4,7 @@ require 'spec_helper' ...@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe API::ImportBitbucketServer do RSpec.describe API::ImportBitbucketServer do
let(:base_uri) { "https://test:7990" } let(:base_uri) { "https://test:7990" }
let(:user) { create(:user) } let(:user) { create(:user, bio: 'test') }
let(:token) { "asdasd12345" } let(:token) { "asdasd12345" }
let(:secret) { "sekrettt" } let(:secret) { "sekrettt" }
let(:project_key) { 'TES' } let(:project_key) { 'TES' }
......
...@@ -36,16 +36,6 @@ RSpec.describe 'Git HTTP requests' do ...@@ -36,16 +36,6 @@ RSpec.describe 'Git HTTP requests' do
end end
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 context "when user is blocked" do
let(:user) { create(:user, :blocked) } let(:user) { create(:user, :blocked) }
...@@ -68,6 +58,26 @@ RSpec.describe 'Git HTTP requests' do ...@@ -68,6 +58,26 @@ RSpec.describe 'Git HTTP requests' do
end end
end end
shared_examples 'operations are not allowed with expired password' do
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
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
shared_examples 'pushes require Basic HTTP Authentication' do shared_examples 'pushes require Basic HTTP Authentication' do
context "when no credentials are provided" do context "when no credentials are provided" do
it "responds to uploads with status 401 Unauthorized (no project existence information leak)" do it "responds to uploads with status 401 Unauthorized (no project existence information leak)" do
...@@ -95,15 +105,6 @@ RSpec.describe 'Git HTTP requests' do ...@@ -95,15 +105,6 @@ RSpec.describe 'Git HTTP requests' do
expect(response.header['WWW-Authenticate']).to start_with('Basic ') expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end end
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 end
context "when authentication succeeds" do context "when authentication succeeds" do
...@@ -212,6 +213,7 @@ RSpec.describe 'Git HTTP requests' do ...@@ -212,6 +213,7 @@ RSpec.describe 'Git HTTP requests' do
it_behaves_like 'pulls require Basic HTTP Authentication' it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication' it_behaves_like 'pushes require Basic HTTP Authentication'
it_behaves_like 'operations are not allowed with expired password'
context 'when authenticated' do context 'when authenticated' do
it 'rejects downloads and uploads with 404 Not Found' do it 'rejects downloads and uploads with 404 Not Found' do
...@@ -306,6 +308,7 @@ RSpec.describe 'Git HTTP requests' do ...@@ -306,6 +308,7 @@ RSpec.describe 'Git HTTP requests' do
it_behaves_like 'pulls require Basic HTTP Authentication' it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication' it_behaves_like 'pushes require Basic HTTP Authentication'
it_behaves_like 'operations are not allowed with expired password'
context 'when authenticated' do context 'when authenticated' do
context 'and as a developer on the team' do context 'and as a developer on the team' do
...@@ -473,6 +476,7 @@ RSpec.describe 'Git HTTP requests' do ...@@ -473,6 +476,7 @@ RSpec.describe 'Git HTTP requests' do
it_behaves_like 'pulls require Basic HTTP Authentication' it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication' it_behaves_like 'pushes require Basic HTTP Authentication'
it_behaves_like 'operations are not allowed with expired password'
end end
context 'but the repo is enabled' do context 'but the repo is enabled' do
...@@ -488,6 +492,7 @@ RSpec.describe 'Git HTTP requests' do ...@@ -488,6 +492,7 @@ RSpec.describe 'Git HTTP requests' do
it_behaves_like 'pulls require Basic HTTP Authentication' it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication' it_behaves_like 'pushes require Basic HTTP Authentication'
it_behaves_like 'operations are not allowed with expired password'
end end
end end
...@@ -508,6 +513,7 @@ RSpec.describe 'Git HTTP requests' do ...@@ -508,6 +513,7 @@ RSpec.describe 'Git HTTP requests' do
it_behaves_like 'pulls require Basic HTTP Authentication' it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication' it_behaves_like 'pushes require Basic HTTP Authentication'
it_behaves_like 'operations are not allowed with expired password'
context "when username and password are provided" do context "when username and password are provided" do
let(:env) { { user: user.username, password: 'nope' } } let(:env) { { user: user.username, password: 'nope' } }
...@@ -1003,6 +1009,24 @@ RSpec.describe 'Git HTTP requests' do ...@@ -1003,6 +1009,24 @@ RSpec.describe 'Git HTTP requests' do
it_behaves_like 'pulls are allowed' it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed' it_behaves_like 'pushes are allowed'
context "when password is expired" do
it "responds to downloads with status 200" 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(:ok)
end
end
it "responds to uploads with status 200" 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(:ok)
end
end
end
end end
end end
end end
......
...@@ -76,7 +76,7 @@ RSpec.describe Repositories::ChangelogService do ...@@ -76,7 +76,7 @@ RSpec.describe Repositories::ChangelogService do
recorder = ActiveRecord::QueryRecorder.new { service.execute } recorder = ActiveRecord::QueryRecorder.new { service.execute }
changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
expect(recorder.count).to eq(11) expect(recorder.count).to eq(12)
expect(changelog).to include('Title 1', 'Title 2') expect(changelog).to include('Title 1', 'Title 2')
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment