THREESCALE-8916: Make strong passwords mandatory#4195
THREESCALE-8916: Make strong passwords mandatory#4195jlledom wants to merge 9 commits into3scale:masterfrom
Conversation
da18ea7 to
5b00c7f
Compare
| self.password = unencrypted_password | ||
| ThreeScale::Analytics.user_tracking(self).track('Migrated to BCrypt') | ||
| update_columns(password_digest: password_digest, salt: nil, crypted_password: nil) | ||
| authenticate(unencrypted_password) |
There was a problem hiding this comment.
I feel uneasy about this whole file. What is it doing? Why is it wrapping? What was super?
| public_search account_plans_ui_visible change_account_plan_permission service_plans_ui_visible | ||
| change_service_plan_permission enforce_sso | ||
| useraccountarea_enabled hide_service signups_enabled account_approval_required public_search | ||
| account_plans_ui_visible change_account_plan_permission service_plans_ui_visiblechange_service_plan_permission |
There was a problem hiding this comment.
| account_plans_ui_visible change_account_plan_permission service_plans_ui_visiblechange_service_plan_permission | |
| account_plans_ui_visible change_account_plan_permission service_plans_ui_visible change_service_plan_permission |
33fe198 to
25f4a95
Compare
891e8cb to
35acc2b
Compare
| authorize! :update, user | ||
|
|
||
| user.update_with_flattened_attributes(flat_params) | ||
| user.update_with_flattened_attributes(flat_params, as: current_user.try(:role)) |
There was a problem hiding this comment.
PUT /admin/api/users/{id}.xml is adding this to it's call. This endpoint is the equivalent but for buyer users: PUT /admin/api/accounts/{id}/users/{id}.xml.
This is to unify behavior.
There was a problem hiding this comment.
I am not sure this is relevant here. From what I can see this as: is used to prevent member permissions from being updated by member users - it's only allowed to admin users:
Lines 112 to 116 in 986e1a3
However, these member permissions thing is only applicable to admin users, not to buyer users, so I don't think adding as: changes anything.
It is true that on the code level there is no distinction between buyer users and admin users...
But I wouldn't add this 🤷
There was a problem hiding this comment.
But I wouldn't add this 🤷
By "this" you mean the as: in buyers_users_controller.rb:40? or the as: in user.rb:116?
Do you want me to remove this change?
There was a problem hiding this comment.
I meant as: current_user.try(:role) in this controller. Unless this breaks something, of course 🤔
Well, I don't know if we need to remove it, I just find it a bit confusing to have it here, as it is not supposed to have any function for this controller, from what I understand.
| { | ||
| signup_type: partner.signup_type, | ||
| password: permitted_params[:password].presence || SecureRandom.hex, | ||
| password: permitted_params[:password].presence, |
There was a problem hiding this comment.
The user is valid without a password when it's an SSO user. So there's no need to enforce a random password. Also, this random password is not shown to the caller anywhere, so it couldn't be used anyway.
After this change, The SSO users for partners don't have a password, the same as any other SSO user in the project.
| @user = @account.users.build | ||
| @user.email = params[:email] | ||
| @user.password = SecureRandom.hex | ||
| @user.password = params[:password].presence |
There was a problem hiding this comment.
Same, no password required.
| // IMPORTANT: These STRONG_PASSWORD_* constants are duplicated from the backend. | ||
| // The source of truth is app/lib/authentication/by_password.rb. If those constants change in Ruby, | ||
| // you must update them here as well. Do not modify these without updating the backend first. | ||
| const STRONG_PASSWORD_MIN_SIZE = 16 | ||
| const RE_STRONG_PASSWORD = new RegExp(`^[ -~]{${STRONG_PASSWORD_MIN_SIZE},64}$`) | ||
| const STRONG_PASSWORD_FAIL_MSG = `Password must be at least ${STRONG_PASSWORD_MIN_SIZE} characters long, and contain only valid characters.` | ||
|
|
There was a problem hiding this comment.
Enforce the same NIST policy in frontend that we enforce in backend
| validates :lost_password_token, :password_digest, length: { maximum: 255 } | ||
|
|
||
| attr_accessible :password, :password_confirmation | ||
| attr_accessible :password, :password_confirmation, as: %i[default member admin] |
There was a problem hiding this comment.
This is to fix a mass-assignment error from PUT /admin/api/users/{id}.xml. Since the endpoint calls the update with role: :admin. The password was excluded from mass-assignment, because it only allowed the default role.
There was a problem hiding this comment.
I'm not sure I understand... if there is no as: argument here, doesn't it mean that the role set in :as when assigning attributes doesn't matter?
| def validate_password? | ||
| password_digest_changed? || (signup.by_user? && password_digest.blank?) | ||
| end | ||
|
|
||
| def validate_strong_password? | ||
| return false if Rails.configuration.three_scale.strong_passwords_disabled | ||
| return false if signup.sample_data? | ||
|
|
||
| validate_password? | ||
| end |
There was a problem hiding this comment.
This replaces password_required? Because it was pretty confusing:
- Being called from views to decide whether making password inputs required. IMO that's wrong, password inputs are required or not according to their purpose, not to some computed value.
- Being called also to decide whether validate the password or not. Which was wrong as well, since it didn't match all scenarios.
The new methods are tested and return proper values for all known scenarios.
| email_part = email.split('@') | ||
| user_attributes = { email: "#{email_part[0]}+test@#{email_part[1]}", username: 'john', first_name: 'John', | ||
| last_name: 'Doe', password: '123456', password_confirmation: '123456', signup_type: :minimal} | ||
| last_name: 'Doe', password: '123456', password_confirmation: '123456', signup_type: :sample_data} |
There was a problem hiding this comment.
In order to exclude "John Doe" from strong password requirement, I added a new signup type :sample_data to identify it.
There was a problem hiding this comment.
The question is, shouldn't we prevent sample weak passwords in production environment? I think we discussed this somewhere, about how to notify provider of the sample buyer user account. e.g. email/internal messaging.
But I'm not sure we came to an agreement.
There was a problem hiding this comment.
We discussed this via slack, I even suggested to get rid of John Doe, we agreed on keeping the sample data. About the sample data having a weak password, that's another story. I remember you mentioned about sending the password to admin via internal messaging, but nothing was agreed at the end.
| def activate_on_minimal_or_sample_data? | ||
| (minimal_signup? || signup.sample_data?) && password.present? && !account.try!(:bought_account_plan).try!(:approval_required?) |
There was a problem hiding this comment.
This method determines whether the new user must be automatically activated or not. I added the :sample_data case to it. Sample user must be activated by default.
| def provider_requires_strong_passwords? | ||
| # use fields definitons source (instance variable) as backup when creating new record | ||
| # and there is no provider account (its still new record and not set through association.build) | ||
| if source = fields_definitions_source_root | ||
| source.settings.strong_passwords_enabled? | ||
| end | ||
| end |
| <%= form.fields_for [:user, @user ] do |user| %> | ||
| <%= user.user_defined_form %> | ||
| <%= user.input :password, as: :patternfly_input, required: true %> | ||
| <%= user.input :password, as: :patternfly_input, input_html: { type: 'password' }, required: true %> |
There was a problem hiding this comment.
Password input was in clear text
| <%= form.input :password, required: false, input_html: { autocomplete: 'off' } %> | ||
| <%= form.input :password_confirmation, required: false, input_html: { autocomplete: 'off' } %> |
There was a problem hiding this comment.
This was wrong, there's no scenario when it's required to change a password in this form.
There was a problem hiding this comment.
In fact, it was a "fake" requirement, the form submit was working without provided values.
| = form.input :password, as: :patternfly_input, | ||
| label: t('.new_password_label'), | ||
| required: false, | ||
| input_html: { value: '', type: 'password', required: false } | ||
|
|
||
| - if using_password | ||
| - if current_user.using_password? |
There was a problem hiding this comment.
Password change inputs are now always visible. However, the current_password input is only visible if the user already has a password in the DB.
| paths = [ | ||
| '''test/unit/authentication/by_password_test.rb''', | ||
| '''test/integration/admin/api/buyers_users_controller_test.rb''' | ||
| ] |
There was a problem hiding this comment.
Had to add this so our password leak system allows me to commit.
| And I fill in "Current password" with "superSecret1234#" | ||
| And I press "Update Details" | ||
| Then field "New password" has inline error "is too short (minimum is 6 characters)" | ||
| Then I should see the error that the password is too weak No newline at end of file |
There was a problem hiding this comment.
no new line char at end ?!?!?!??!?
There was a problem hiding this comment.
Please accept my most sincere apologies: 4966a16
There was a problem hiding this comment.
I hope heaven forgives you because I cannot
|
|
||
| def using_password? | ||
| password_digest.present? | ||
| password_digest_in_database.present? |
There was a problem hiding this comment.
wait, if password_digest was set but not yet in the database, shouldn't we still return true here?
There was a problem hiding this comment.
It was causing issues in the user edit form. If I recall correctly, on some scenarios the form required the user to provide their current password in situations where that password was still not persisted, so it couldn't work.
I think it's correct this way. We only consider a user is using a password when it indeed has a password in DB.
There was a problem hiding this comment.
Ok, fine, if not too annoying, I'd suggest renaming the method to already_using_password? to make this immediately obvious.
There was a problem hiding this comment.
I renamed it everywhere except in the User liquid drop, to not break existing dev portals 8b6697b
|
|
||
| def validate_strong_password? | ||
| return false if Rails.configuration.three_scale.strong_passwords_disabled | ||
| return false if signup.sample_data? |
There was a problem hiding this comment.
| return false if signup.sample_data? | |
| return false if signup.sample_data? && !Rails.env.production? |
Follow up on previous question about sample data ending up in production, I would imagine the logic to something like this.
There was a problem hiding this comment.
Instead of this, is we agreed to not allow weak passwords for sample dat ain production, I'd prefer to completely remove the :sample_data signup type. I wouldn't be needed anymore.
There was a problem hiding this comment.
I would be happy not having :sample_data login type. After all the idea behind sample data is to look like real data and being a separate type is not it.
There was a problem hiding this comment.
Yeah, it'd be good to remove the sample data signup type, but that would imply all the additional logic: Creating Jonh Doe with a random password, send it via internal message to admin, and updating the places where that password is visible, like the CMS toolbar.
@mayorova WDYT?
There was a problem hiding this comment.
I vote for keeping it like it is now - John Doe with 123456 password still being valid. I think it's good to keep the changes less disruptive for the user.
There was a problem hiding this comment.
We can also generate default passwords derived from e.g., buyer id and SECRET_KEY_BASE derivative and we can present that password to the provider in the UI when viewing the user.
- provider opens the user
- system detects that this is the first default user John Doe
- system generates the default password
- checks whether it matches the user
- warns that the user has the default password of "..." OR reports that the password has changed from the default
I think this will be a good UX while keeping things way more secure.
| \z | ||
| /x | ||
| STRONG_PASSWORD_FAIL_MSG = "Password must be at least 8 characters long, and contain both upper and lowercase letters, a digit and one special character of #{SPECIAL_CHARACTERS}.".freeze | ||
| STRONG_PASSWORD_MIN_SIZE = 15 |
There was a problem hiding this comment.
it might be a good idea in case some customers want to apply higher level of security, as you wish.
| STRONG_PASSWORD_MIN_SIZE = 15 | |
| STRONG_PASSWORD_MIN_SIZE = ENV["THREESCALE_PASSWORD_MIN_SIZE", 15] |
There was a problem hiding this comment.
That's fine, but we need to keep the client side in sync as well. I don't know if there's a non-dirty way to make the JS code take a value from Rails config. I'll take a look.
There was a problem hiding this comment.
Server side will reject it anyway. I don't want to make things more complicated. Just a quick workaround to have available if somebody needs. But if you find a way to keep UI in sync, that's fine.
There was a problem hiding this comment.
Since we use client-side rendering, the UI is mounted in an environment which is isolated from the ruby backend and has no access to it's data or config unless explicitly shared somehow. For instance, we don't use localization anymore from react components, we hardcode the strings on React components.
Due to this, the options we have to keep min size synced between client and server are:
- Add the password min size as a global variable in the DOM
windowobject, we would need to edit the layout template for that. - Use the
dataattribute from views, when mounting the React components. That would require editing all view templates for screens that include the password component and pass the pass size down through all the chain until the validator.
Both approaches seem pretty dirty to me. I prefer to just hardcode it to 15 in both client and server sides. Equally dirty, but much less complex.
There was a problem hiding this comment.
What if we allow server to have that changed to more than 15 characters. UI stays as it is. If a customer needs to override, shorter passwords will fail on server validation. But they will still have the security settings they require although not ideal UX. That was my line of thinking. Not about allowing shorter password but only for longer where the UI will not stop then from entering a longer one.
There was a problem hiding this comment.
Nah, I don't like that. I prefer UI and backend to be in sync, the opposite would be pretty confusing for client ad for future us.
| assert_equal body['success'], true | ||
| end | ||
|
|
||
| test 'post without password creates user with no password' do |
There was a problem hiding this comment.
Can this user without a password login afterwards? I hope not before resetting its password.
There was a problem hiding this comment.
Yeah, users without passwords can't login until they set a password. For this particular endpoint about partners, the endpoint requires setting either a password or an open_id. But users with open_id like the one in this test shouldn't be required to have a password in DB.
1138886 to
bc746e2
Compare
| return false if Rails.configuration.three_scale.strong_passwords_disabled | ||
| return false if signup.sample_data? | ||
|
|
||
| validate_password? |
There was a problem hiding this comment.
Do we need to call this again?
As we have
validates_presence_of :password, if: :validate_password?
validates :password, length: { minimum: STRONG_PASSWORD_MIN_SIZE }, if: :validate_strong_password?
It means that most for when strong password is used (most of the time) validatee_password? will be called twice?
There was a problem hiding this comment.
It will be called not twice, but three times, because it's also called by:
But I think that's fine because it's only called when updating a user, I don't know how common is that but doesn't seem supper common. Also, it makes the code more clear, otherwise, it would be confusing, e.g. a scenario where validate_password? is false but validate_strong_password? is true. Like for instance when you are modifying a user attribute which is not the password. What would happen then? would we validate the length of a password when that attribute is not involved in the update? Even more, password is a virtual attribute so I don't really know what would be there at the validation moment, most probably nil I guess.
This way validate_password? and validate_strong_password? always have consistent values.
akostadinov
left a comment
There was a problem hiding this comment.
This is a great work, thank you for that!
I added some comments, but can be merged on your discretion.
I think that in a future PR it will be very valuable to also implement the following changes:
- the default developer user should have a strong password and a mechanism for the provider account to know it. We may as well have a mechanism to generate default passwords based on provider id and some
SECRET_KEY_BASEderived key so that we can show that in the provider portal. - remove the signup types, it will be less error prone if we treat everybody the same
| def test_weak_password_rejected_when_strong_passwords_enabled | ||
| post signup_path, params: account_params(WEAK_PASSWORD) | ||
|
|
||
| assert_response :success |
There was a problem hiding this comment.
Shouldn this be something unsuccessful? Seems wrong to return success when signup failed 🤔
There was a problem hiding this comment.
Not really, because this is called from UI so one expects either :success or :redirect. In the case of error, the controller returns :success and renders the form again.
There was a problem hiding this comment.
I thought that we don't support partner accounts anymore but whatever, test can stay for the time being before we fully remove the functionality.
There was a problem hiding this comment.
If we don't support partners, that's actually better: we have one less thing to worry about. I'd be good to find out whether partners are actually not supported anymore so we can remove the logic.
It makes sense they are not supported because the way to create a partner is too cumbersome: you must create it directly form the rails console and then manually change the system_name attribute, since the generated one prevents creating tenant domains for new partner tenants.
There was a problem hiding this comment.
I don't remember where but surely I read somewhere that we don't support them.
There was a problem hiding this comment.
| assert_match(/incorrect/i, flash[:danger]) | ||
| end | ||
|
|
||
| test 'user with password can update when providing correct current password' do |
There was a problem hiding this comment.
I wonder if we should require current password for sso users but perhaps that's the safer option 🤔
There was a problem hiding this comment.
Current password will be asked for any user that actually has a password in DB, no matter if they are SSO users or not. About SSO users with no password, there's no password we can require.
There was a problem hiding this comment.
Maybe I didn't understand the test. I only see:
put :update, params: { user: { current_password: 'superSecret1234#', username: 'newusername' } }
So what is beingupdated? I don't see new password set. I assumed this current_password was only the new password.
There was a problem hiding this comment.
In this test we are updating username. What the test asserts is we can't update our personal details without introducing our current password. I'm not sure how useful is this. Because I understand we should ask for the previous password if we are setting a new one, but there's no need to ask for the password to change other attributes, like the username. However, that's what we are enforcing.
There was a problem hiding this comment.
ok, got it, I read the test as intending to change the password. Fine with me.
| assert @user.reload.authenticated?('new_password_123') | ||
| end | ||
|
|
||
| test 'SSO user without password can set password when enforce SSO is enabled' do |
There was a problem hiding this comment.
seems like a dup of SSO user can set password without providing current password above
There was a problem hiding this comment.
Yeah, they test the same, but the difference is the context. In one of them, SSO is enforced, in the other is not. I think both are relevant to catch regressions, in particular with passwrods tricky logic. The more tests the better.
| end | ||
|
|
||
| def validate_strong_password? | ||
| return false if Rails.configuration.three_scale.strong_passwords_disabled |
There was a problem hiding this comment.
I think it will be much better to set the STRONG_PASSWORD_MIN_SIZE based on this setting than overriding it here.
Because you said in a comment:
Nah, I don't like that. I prefer UI and backend to be in sync, the opposite would be pretty confusing for client ad for future us.
But this setting is essentially doing the same - UI will still want strong passwords but backend will allow weaker.
There was a problem hiding this comment.
But this setting is essentially doing the same - UI will still want strong passwords but backend will allow weaker.
You're right on this
I think it will be much better to set the STRONG_PASSWORD_MIN_SIZE based on this setting than overriding it here.
What do you mean by "overriding here"? overriding the setting? Also, if we set STRONG_PASSWORD_MIN_SIZE based on the setting, we still need this method to check for sample data.
There was a problem hiding this comment.
Yes, I meant that this specific line overrides STRONG_PASSWORD_MIN_SIZE based on a configuration setting. So my suggestion was to simplify by setting STRONG_PASSWORD_MIN_SIZE based on a configuration value instead of moving the logic here.
Agreed that we still need the sample data check. Hopefully later we get rid of it by also generating strong password for John Doe.
There was a problem hiding this comment.
then we would need to get rid of john doe default password and signup_types before implementing your suggestion
There was a problem hiding this comment.
We can get rid of this specific line regardless of John Doe.
There was a problem hiding this comment.
I've been trying but I don't think it's worth it right now. Having the setting checked in the method is pretty convenient for tests, so I can stub the value and easily see the different behavior. However, if we move the logic to the macro, then the setting is only read once when loading the code, so stubbing the setting has no effect, which leads to a few tests failing.
I prefer to not spend the time redefining tests right now, when we actually get rid of sample data, I'll remove the validate_strong_password? method and then I'll worry about the tests.
There was a problem hiding this comment.
Ok, sure. Even if it stays, it will not be a big issue.
|
|
||
| def validate_strong_password? | ||
| return false if Rails.configuration.three_scale.strong_passwords_disabled | ||
| return false if signup.sample_data? |
There was a problem hiding this comment.
We can also generate default passwords derived from e.g., buyer id and SECRET_KEY_BASE derivative and we can present that password to the provider in the UI when viewing the user.
- provider opens the user
- system detects that this is the first default user John Doe
- system generates the default password
- checks whether it matches the user
- warns that the user has the default password of "..." OR reports that the password has changed from the default
I think this will be a good UX while keeping things way more secure.
| test 'new user with password_digest' do | ||
| user = user_with_password.call('superSecret1234#') | ||
|
|
||
| assert user.authenticated?('superSecret1234#') |
There was a problem hiding this comment.
I think the original method represents the intention much better reads much better.
| assert user.authenticated?('superSecret1234#') | |
| assert user.authenticate('superSecret1234#') |
I mean I can't understand the whole authenticated? alias. Maybe something like authenticates_by?("password") would be ok. Still I find using the standard method preferable.
But well that might only be me 🤷
There was a problem hiding this comment.
Yeah I understand, but the alias comes from the ancients. We could get rid of it but not in this PR I think. I'm calling authenticate from the tests I modified in this PR: d2dbd76
There was a problem hiding this comment.
While working on this I found there was a precedence bug in the authenticate definition. I fixed it here: 1b05dc3
There was a problem hiding this comment.
Here, are you happy now? https://redhat.atlassian.net/browse/THREESCALE-12469
There was a problem hiding this comment.
I'm happy either way. But I'm more happy now, thanks!
8dceb4b to
77fc304
Compare
I'm fine with creating a strong password for john doe, but how do you suggest to generate the derivative exactly?
Fine for me. |
Something like this should be fine I think module InitialDeveloperPassword
# SALT used like the `purpose` in `MessageVerifier`
SALT = "onboarding.v1.sample-developer-password"
KEY_BYTES = 32 # 256-bit subkey
LENGTH = 12 # password length
def self.subkey
# Uses Rails.application.secret_key_base under the hood
@subkey = Rails.application.key_generator.generate_key(SALT, KEY_BYTES)
end
def self.payload_for(user)
[user.id, user.created_at.to_i].join(":")
end
def self.for(user)
mac = OpenSSL::HMAC.digest("SHA256", subkey, payload_for(user))
Base64.urlsafe_encode64(mac, padding: false)[0, LENGTH]
end
endMaybe this should be a service or idk where to put it, but in general I think this is a reasonable way to do it. I think more work will be adjusting the UI to:
|
@akostadinov https://redhat.atlassian.net/browse/THREESCALE-12470 |
akostadinov
left a comment
There was a problem hiding this comment.
Thank you, really nice!
I saw a couple of minor nitpicks but feel free to ignore 👼
| } | ||
| end | ||
|
|
||
| def test_weak_password_rejected_when_strong_passwords_enabled |
There was a problem hiding this comment.
why not use test "weak password rejected when strong passwords enabled"?
just another minor nitpick
There was a problem hiding this comment.
Claude did it 😞
cec3bf7 to
c8c8e99
Compare
- Validate strong passwords in React forms - Some forms treat the password field as clear text. - Make password fields required only when they are actually required. - `Current password` field visible and required only when it should. - Password can be set for all users, no matter how did they signup. - SSO Users: don't see the toast to change password. It can be changed from the same form This fixes https://issues.redhat.com/browse/THREESCALE-11548 - When enforce SSO is enabled, passwords can still be set or changed.
This is to exclude John Doe from strong passwords validation Co-Authored-By: Claude <noreply@anthropic.com>
c8c8e99 to
ae75cc5
Compare
|
Rebased and fixed conflicts |

Note
This PR includes all changes from #4194. To make it easier to review, jlledom#3 includes only the actual diff to make strong passwords mandatory.
What this PR does / why we need it:
Currently, a provider can enforce strong password for the developer portal, and weak passwords are accepted by default. About admin portal, there's no option to enable strong passwords, weak passwords are always accepted.
I think both situations make no sense. I don't think it's acceptable to allow users decide whether they enforce strong passwords or not, as long as strong passwords are possible, that should be the default. And same thing about admin portal.
After a discussion via slack, we agreed on this terms:
settings.ymlto allow weak passwords for usstrong_passwords_disabled: truetosettings.yml. If not set at all, strong passwords are enabled, so clients don't have to change anything on their side.db:seedshould accept weak passwordsstrong_passwords_disabledtotruebefore running the task:smaple_data. Sample data accepts weak passwords, no matter the setting.This affects multiple screens, but also API endpoints, this is the complete list:
Additionally, I also added some behavioral changes to the UI. To solve bugs or behaviors I think are incorrect. This is the summarty Claude generated:
Screens/Forms Modified
app/javascript/src/Login/utils/validations.ts)length: { minimum: 6 }to regex pattern matching backend rulesSTRONG_PASSWORD_*constants mirroring backend.app/views/buyers/accounts/new.html.erb)type: 'password'to password field. Previously the field was showing password as clear text.app/views/buyers/users/edit.html.erb)required: trueto `required:app/views/provider/admin/account/users/_form.html.erb)required: truetorequired: false. Password is now optional when editing a provider user.app/views/provider/admin/user/personal_details/edit.html.slim)1. Removed the toast/alert prompting SSO users to set password via reset form
2. Password field now always visible (not conditional on
using_password)3. Password field changed to
required: false4. Added
type: 'password'to hide password input5. "Current password" section only shown when
using_password?is true (users with existing password must confirm it)config/locales/en.yml)set_password_htmltranslation (toast was removed).Summary of behavioral changes
Strong password validation in React - Frontend now enforces the same 16+ character ASCII-printable password rules as the backend.
Password fields show as masked - Fixed forms that were accidentally displaying passwords as plain text.
Password fields are optional on edit - When editing existing users (buyer or provider), password is no longer required. You can update other fields without changing the password.
Password field always visible - On personal details page, the password field is now always shown regardless of how the user signed up (SSO, password, etc.). Any user can set/change their password.
Current password only required when applicable - The "Current password" confirmation field only appears when the user already has a password set (
using_password?). New SSO users setting a password for the first timedon't need to provide a current password.
Removed SSO password reset toast - SSO users no longer see the banner telling them to use the password reset form. They can now set their password directly from the personal details form.
Works with enforce SSO - Passwords can still be set/changed even when enforce SSO is enabled.
I know this is a though one. I recommend to review the commits one by one. Also, I'll add some comments to provider further explanations.
Which issue(s) this PR fixes
https://issues.redhat.com/browse/THREESCALE-8916
https://issues.redhat.com/browse/THREESCALE-11548
Verification steps
You can go through any (ideally all) screens above an try to set a weak password. Also tests should pass.
List of added tests
This is the complete list of tests added in this PR. To cover all the new behaviour explained above:
test/unit/authentication/by_password_test.rbvalidate_password?,validate_strong_password?, andusing_password?methods. Covers scenarios for different signup types (new_signup, minimal, sample_data, machine), password change detection, and strong password validation bypass for sample_data users.test/unit/liquid/drops/user_drop_test.rbusing_password?andpassword_required?Liquid drop methods, verifying correct behavior for SSO users, by_user signups, and sample_data signups.test/unit/user/signup_type_test.rbsample_data?,machine?, andby_user?methods to verify sample_data signup type is correctly identified and classified as a machine signup.test/unit/user/states_test.rbActivateOnMinimalOrSampleDataTestclass with tests for auto-activation logic, covering minimal and sample_data signups with/without password and approval requirements.test/unit/logic/provider_signup_test.rbsample_datasignup type, bypasses strong password validation, and is auto-activated.test/integration/developer_portal/signup_test.rbStrongPasswordsTestclass testing developer portal signup with strong password validation enabled/disabled.test/integration/provider/signups_controller_integration_test.rbSignupsControllerStrongPasswordsTestclass testing admin portal provider signup with strong password validation enabled/disabled.test/integration/provider/admin/account/users_controller_test.rbtest/functional/buyers/users_controller_test.rbEditPagePasswordFieldsTestclass verifying password fields are always visible on buyer user edit page, including for SSO users.test/functional/provider/admin/user/personal_details_controller_test.rbSSOUserWithoutPasswordTest,UserWithPasswordTest, andEnforceSSOEnabledTestclasses testing personal details page behavior for SSO users, current password requirements, and enforce SSO scenarios.test/functional/partners/providers_controller_test.rbtest/functional/partners/users_controller_test.rbtest/integration/admin/api/buyers_users_controller_test.rbtest/integration/user-management-api/users_test.rbPUT /admin/api/users/{id}endpoint.