Skip to content

Commit e400fd7

Browse files
Allow changing password if it exists
Previously we'd be hiding the "change password" dialog on the basis of an external authentication method existing. However, that's not enough, because (at least with user remapping enabled) it's possible that a user that logged in via password once, gained the ability to login through SSO afterwards. Such a user then can use both mean to authenticate, thus they also need to be able to change a potentially compromised password. Much more work is needed here: Users need to be aware that their password still works, they need to be able to delete a password if they only want to use SSO and maybe there's also a use case for deleting an SSO association and going back to password-based logins. However, all of these things require more UI changes and some proper product development first. This change is a first step to improve the situation.
1 parent fe2c3f9 commit e400fd7

File tree

4 files changed

+45
-5
lines changed

4 files changed

+45
-5
lines changed

app/models/user.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,8 @@ def check_password?(clear_password, update_legacy: true)
369369

370370
# Does the backend storage allow this user to change their password?
371371
def change_password_allowed?
372-
return false if uses_external_authentication? ||
373-
OpenProject::Configuration.disable_password_login?
372+
return false if OpenProject::Configuration.disable_password_login?
373+
return false if uses_external_authentication? && current_password.nil?
374374

375375
ldap_auth_source_id.blank?
376376
end

spec/controllers/users_controller_spec.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,7 +787,7 @@
787787
current_user: :user
788788

789789
context "with external authentication" do
790-
let(:some_user) { create(:user, identity_url: "some:identity") }
790+
let(:some_user) { create(:user, :passwordless, identity_url: "some:identity") }
791791

792792
before do
793793
as_logged_in_user(admin) do
@@ -801,6 +801,21 @@
801801
end
802802
end
803803

804+
context "with external authentication and an existing password" do
805+
let(:some_user) { create(:user, identity_url: "some:identity") }
806+
807+
before do
808+
as_logged_in_user(admin) do
809+
put :update, params: { id: some_user.id, user: { force_password_change: "true" } }
810+
end
811+
some_user.reload
812+
end
813+
814+
it "accepts setting force_password_change" do
815+
expect(some_user.force_password_change).to be(true)
816+
end
817+
end
818+
804819
context "with ldap auth source" do
805820
let(:ldap_auth_source) { create(:ldap_auth_source) }
806821

spec/factories/user_factory.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@
7979
end
8080
end
8181

82+
trait :passwordless do
83+
password { nil }
84+
password_confirmation { nil }
85+
end
86+
8287
factory :admin, parent: :user, class: "User" do
8388
firstname { "OpenProject" }
8489
sequence(:lastname) { |n| "Admin#{n}" }

spec/features/auth/lost_password_spec.rb

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@
9191
end
9292
end
9393

94-
context "when user has identity_url" do
94+
context "when user only authenticates via SSO" do
9595
let!(:provider) { create(:saml_provider, slug: "saml", display_name: "The SAML provider") }
96-
let!(:user) { create(:user, authentication_provider: provider) }
96+
let!(:user) { create(:user, :passwordless, authentication_provider: provider) }
9797

9898
it "sends an email with external auth info" do
9999
visit account_lost_password_path
@@ -111,4 +111,24 @@
111111
expect(mail.body.parts.first.body.to_s).to include "(The SAML provider)"
112112
end
113113
end
114+
115+
context "when authenticates via password & SSO" do
116+
let!(:provider) { create(:saml_provider, slug: "saml", display_name: "The SAML provider") }
117+
let!(:user) { create(:user, authentication_provider: provider) }
118+
119+
it "sends an email with external auth info" do
120+
visit account_lost_password_path
121+
122+
# shows same flash for invalid and existing users
123+
fill_in "mail", with: user.mail
124+
click_on "Submit"
125+
126+
expect_flash(message: I18n.t(:notice_account_lost_email_sent))
127+
128+
perform_enqueued_jobs
129+
expect(ActionMailer::Base.deliveries.size).to be 1
130+
mail = ActionMailer::Base.deliveries.first
131+
expect(mail.subject).to eq I18n.t("mail_subject_lost_password", value: Setting.app_title)
132+
end
133+
end
114134
end

0 commit comments

Comments
 (0)