Repository navigation
self service improvement - #4100
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Account verification has a credential race, and the configured signup redirect fallback is lost.
4 open findings
What changed in this PR
Updates self-service signup so activation verifies email ownership, invalidates the registration password, and redirects users through password setup.
Changes:
- Generates a reset code during account activation.
- Adds forced-password messaging and updates browser flows.
- Expands unit, MockMvc, and integration coverage.
| File | Description |
|---|---|
server/.../AccountCreationService.java |
Adds reset code to activation responses. |
server/.../AccountsController.java |
Redirects activation to password setup. |
server/.../EmailAccountCreationService.java |
Invalidates signup passwords and creates reset codes. |
server/.../ResetPasswordController.java |
Adds forced-change page messaging. |
server/.../AccountsControllerTest.java |
Updates activation redirect tests. |
server/.../EmailAccountCreationServiceTests.java |
Updates service construction and reset mocks. |
uaa/.../AccountsControllerMockMvcTests.java |
Covers the new default-zone flow. |
uaa/.../AccountsControllerMockMvcZonePathTests.java |
Covers zone-path activation flows. |
uaa/.../CreateAccountIT.java |
Updates end-to-end signup scenarios. |
uaa/.../IntegrationTestUtils.java |
Completes password setup in Selenium fixtures. |
uaa/src/main/resources/messages.properties |
Adds forced-change text. |
docs/UAA-Configuration-Reference.md |
Documents the changed signup behavior. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
838c001 to
6b46280
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Reset-code generation can fail after verification, leaving an activated account with an unknown password and no retryable activation link.
1 open finding
4 resolved since last review
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
6b46280 to
db06278
Compare
020b13e to
472c9ce
Compare
|
This PR exceeds the recommended size of 1000 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
ca187f8 to
f929bf5
Compare
Confirming the email address of a self-service signup no longer activates the password entered on the signup form. The account is verified after that password is replaced, and the user is sent to the password reset page (force_change=true) to set a password. A signup redirect (an explicit redirect_uri or the client's signup_redirect_url) is applied after the password is set.
… reset - Email change verification only redirects to a requested redirect_uri that matches a registered redirect URI of the client, or to the client's change_email_redirect_url; otherwise the user goes to the profile page (or the login page when signed out). - The URL carried to /login as form_redirect_uri is URL-encoded after email change, invitation acceptance and password reset, so it arrives as one parameter.
- POST /Codes requires a non-blank intent and stores it as given; a missing or
blank intent is answered with 400. Documented in UAA-APIs.rst and the API
docs.
- Passcode login requires the intent the passcode endpoint stores
("PASSCODE <user id>").
- Opening a password reset link, or submitting a rejected password, no longer
extends the lifetime of the reset code; opening the link again shows the
form.
…n links from the user API
- POST /create_account.do and POST /change_email.do answer the same way for an
address that already has an account as for a new address. The owner of the
address receives an email (account_exists.html) instead of an activation or
verification link. The signup confirmation page names the address the email
was sent to.
- The duplicate-email handling in ChangeEmailController that can no longer be
reached and the email_change.username_exists message are removed.
- A link from GET /Users/{id}/verify-link only verifies the user: the password
set by the caller keeps working, and the user is redirected to
/login?success=verify_success.
- The password reset code on AccountCreationResponse is not serialized.
- Docs: login.selfServiceLinksEnabled in UAA-Configuration-Reference.md.
- The password change page guard treats /force_password_change/ like
/force_password_change, and a password change submit is refused when no
change is pending.
- A zone path request (/z/{subdomain}/...) must come from a recognized host,
like any other request; otherwise it is answered with 404, and the message
names the hostname.
- Docs: zones.paths.enabled and zones.internal.hostnames in
UAA-Configuration-Reference.md.
…cim.invite.allow_reinvite - An invited user who accepts through SAML must supply an email that matches the invited address, unless the user is a fresh invitee (unverified, username equal to the email). - The invitation code re-issued during LDAP acceptance keeps its intent. - New property scim.invite.allow_reinvite (default true, unchanged behavior). With false, POST /invite_users reports a verified user under failed_invites (user.already-verified) when the caller holds scim.invite but cannot read users (scim.read or zone admin). - Docs: invitations accepted through an external identity provider, the client attributes signup_redirect_url and change_email_redirect_url, and the new property.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved activation-link and SAML invitation vulnerabilities can overwrite users’ passwords or bind an existing account to an unrelated identity.
5 open issues
Define an upgrade policy for legacy verification links · New Block activation links after the account has been verified · New Require a newly created invited user for missing-email fallback · New Preserve trusted signup redirects through password setup · New Handle code expiry during retry generation as an invalid code · New
1 resolved since last review
Review effort: Balanced
… of backwards compatibility to minimize breaking changes. #4100 (review)
…reset code expiring during a submit Opening another activation link for an account that is already confirmed keeps the password the owner has set and asks for no further change. A client's signup_redirect_url applies after the owner sets a password, also when it is not one of the client's registered redirect URIs. Any other unregistered redirect still falls back to home. A reset password submit that fails validation after the code has expired is reported as an invalid code, instead of failing while the code is issued again. #4100 (review)
…eset code expiring during a submit Completing activation for an account that is already verified changes nothing: no new password, no password change flag and no reset code. The user is sent to the login page with the verify_success message. Resetting a password follows the stored redirect when it is the client's signup_redirect_url, which an administrator sets on the client, also when it is not a registered redirect URI. Any other redirect still has to match a registered redirect URI. When a reset password submit fails validation and the code has expired meanwhile, the user is sent to the forgot password page with the invalid code message, instead of an error while the code is issued again. Docs: the SAML part of the invitation acceptance note says what UAA checks when the assertion has no email attribute, and that mapping the email attribute compares every acceptance with the invited address. The self-service confirmation note describes the signup redirect fallback and opening another activation link. The two tests for SAML acceptance without an email are dropped, as that behavior stays unchanged. The account MockMvc tests also run in another zone (subdomain and zone path). createTestClient takes the signup redirect as a parameter.
a4c8466 to
df2c742
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Human assessment is needed because an unpublished critical activation race could overwrite the owner’s newly established password and invalidate another request’s setup link.
No open issues 🎉
5 resolved since last review
Review effort: Balanced
b00db24 to
d16758a
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The documentation contains three active inconsistencies with the implemented API behavior, including an expired example timestamp, incorrect client_id requirements, and incomplete redirect URI behavior.
4 open issues
1 resolved since last review
Review effort: Lite
d16758a to
e5f9bf2
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
The PR spans security-sensitive authentication, invitation, password-reset, redirect, and zone-routing changes that require unexecuted full integration and real-provider callback validation, including rolling-upgrade coverage.
No open issues 🎉
4 resolved since last review
Review effort: Lite
… verifies the user An activation code issued before codes carried the user_provisioned marker takes the self-service path. That path ends in a reset code that is looked up by username in the uaa origin, so for a user of another origin it could be the reset code of a different account with the same username. Completing activation for a user whose origin is not uaa now only verifies the user, as it did before the self-service path existed: no password change and no reset code.



Self-service signup, invitations, redirects and expiring codes
This pull request changes how a self-service signup is confirmed, how signup and email change answer for an address that
already has an account, how zone path requests are matched to a host, and how several flows handle redirects and
expiring codes. It adds one configuration property,
scim.invite.allow_reinvite, which keeps today's behavior bydefault.
Read before upgrading
Changes that need a check or an action
/reset_password?code=…&force_change=trueand sets a password there. A signup redirect (redirect_urior the client'ssignup_redirect_url) is applied after the password is set. Accounts confirmed throughGET /Users/{id}/verify-linkare not affected: the password set by the caller keeps working and the user is sent to/login?success=verify_success. A link issued before the upgrade carries no marker for this and follows the new path for its one-hour lifetimePOST /create_account.doandPOST /change_email.dono longer answer422withusername_exists. Both go to the "email sent" page, as for a new address, and the owner of the address receives a "You already have an account" email instead of an activation or verification link422or its messagezones.paths.enabled: true, a request to/z/{subdomain}/…whose host is not recognized is answered with404(zone.not.found, bodyCannot find identity zone, unrecognized hostname: <hostname>) instead of being served for the zone in the path. Recognized hosts: the host ofuaa.url, the host oflogin.url,localhost, and every entry ofzones.internal.hostnames. A setup that lists onlylocalhoststill accepts any hostzones.internal.hostnamesPOST /Codesrequires an intent. A request withoutintent, or with a blank one, is answered with400andintent is required. Known intents: EMAIL, INVITATION, REGISTRATION; custom values are accepted.Any other value is stored as given. Codes that already exist without an intent keep working until they expire. Documented indocs/UAA-APIs.rstand in the generated API guide (section "Expiring Codes")POST /Codesthat send no intentintentPASSCODE <user id>); any other code is refused withInvalid passcodePOST /Codesand then used as a passcodeBehavior changes in narrow cases
redirect_urionly when it matches one of the client's registered redirect URIs, or to the client's
change_email_redirect_url.Otherwise the user goes to the profile page, or to the login page when signed out. To keep a redirect, register the
URI or set
change_email_redirect_urlon the client.response without an email is accepted only when the invited account is still unverified and its username equals the
invited email, which is the state of an account created by an invitation that has not been accepted yet. UAA does not
record whether the invitation created the account, so an existing account in the same state is accepted too. Other
cases are refused with a message that names the reason. Map the email attribute at the identity provider if it does
not release one: every acceptance is then compared with the invited address.
GET /Users/{id}/verify-linkissued before the upgradehas no marker saying that the caller set the password. For its one-hour lifetime it follows the new signup path: the
user sets a new password. For a user of another origin it only verifies the user, as before. After the upgrade, new
links are not affected.
form_redirect_uriis URL-encoded. The URL carried to/loginafter an email change, an invitation acceptance ora password reset is percent-encoded, so a URL with
&or#arrives as one parameter. The decoded value is unchanged./force_password_change/is handled like/force_password_change, and a passwordchange submit is refused (redirect to
/) when no password change is pending for the session.original code. No visible change.
New configuration
scim.invite.allow_reinvite(uaa.yml, global), defaulttrue: no change. Withfalse,POST /invite_userslists a user who is already verified under
failed_invites(error codeuser.already-verified, messageThe user is already verified.), with no user id and no link, when the token hasscim.invitebut may not read users(neither
scim.readnor the zone administrator scope). A token that may read users gets the same response as today.Users who are not verified yet and new users are always invited, so an expired invitation can be sent again. Before
setting
false, givescim.readto any client that holdsscim.invite, re-invites existing users and relies on theuser id or the link in the response.
Customizations to check
account_exists.html(the "You already have an account" email).email_sent.activation_address(
Please check {0} for an activation link.).email_change.username_exists; it is no longer used.login.verify_success.login.verify_successis still shown after an account is confirmed throughGET /Users/{id}/verify-link.Known condition
two activation links exist. Opening a second link after the first one was used changes nothing (the password the owner
set stays). If both links are opened within the same few milliseconds, the requests can overlap: both replace the
registration password and the later one invalidates the reset link of the earlier one, so one of the two pages reports
that the link is no longer valid. The owner then uses "Forgot password". Replacing a password the owner has already
set would need one request to stall for as long as the owner takes to type and submit a new password. Making the
transition atomic would need a conditional update across the password, the change-required flag and
verified; it isnot part of this change.
Java API
EmailAccountCreationServicetakes aResetPasswordServiceconstructor argument. Spring wiring is updated; only codethat builds this bean by hand needs a change.
ScimUtils.getExpiringCodehas an overload that takes extra entries for the code data.Commits
9b2013eadfix: confirming a self-service signup leads the user to set a password03313203efix: redirects after email change, invitation acceptance and password resetform_redirect_uriencodingd5ace057ffix: expiring code intents and reset code lifetimePOST /Codes9fb964055fix: signup and email change for an existing address, and verification links from the user APIverify-linkpart of item 1, the reset code onAccountCreationResponseis no longer serialized, docsb42f54825fix: password change page guard and zone path request hostsd755d0457feat: invitations: email for external acceptance, LDAP code intent, scim.invite.allow_reinvitescim.invite.allow_reinvite, docs73f2c1d7e4test: another activation link, an unregistered signup redirect and a reset code expiring during a submitdf2c742b70fix: another activation link, an unregistered signup redirect and a reset code expiring during a submitsignup_redirect_urlapplies after the password is set; an expired reset code during a rejected submit shows the invalid code page; docs2c30db62e3fix: opening an old activation code for a user of another origin only verifies the userDocumentation
docs/UAA-Configuration-Reference.md:scim.invite.allow_reinvite; zone paths andzones.internal.hostnames;self-service confirmation and addresses that already have an account (under
login.selfServiceLinksEnabled); theclient attributes
signup_redirect_urlandchange_email_redirect_url(underoauth.clients).docs/UAA-APIs.rstand the generated API guide (CodeStoreEndpointDocs, section "Expiring Codes"):POST /Codesand itsintent;
redirect_urlofPOST /email_changes;invitations accepted through an external identity provider;
failed_inviteswithscim.invite.allow_reinvite: false.Testing
the full filter chain, in the default zone and in a non-default zone in both zone resolution modes (subdomain and zone
path) where the area has those tests.
CreateAccountIT(confirmation page text),ResetPasswordIT,InvitationsIT(POST /Codeswith an intent), and the shared signup helper inIntegrationTestUtils../gradlew clean test integrationTest --continueon the final tree: unit tests 5578 (server) and 3074 (uaa), integrationtests 3 (server) and 378 (uaa), no failures.