introduce event types to update/remove credentials

Closes #10114

Signed-off-by: Theresa Henze <theresa.henze@bare.id>
This commit is contained in:
Theresa Henze 2024-03-19 16:39:34 +01:00 committed by Alexander Schwartz
parent ba861fc5d7
commit a1c23fef8c
43 changed files with 464 additions and 133 deletions

View file

@ -162,3 +162,9 @@ In {project_name} 26, this feature is enabled by default. This means that all us
It is possible to revert this behavior to the previous state by disabling the feature. Follow the `Volatile user sessions` section in https://www.keycloak.org/server/caching[Configuring distributed caches] guide for more details.
For information on how to upgrade, see the link:{upgradingguide_link}[{upgradingguide_name}].
= New generalized event types for credentials
There are now generalized events for updating (`UPDATE_CREDENTIAL`) and removing (`REMOVE_CREDENTIAL`) a credential. The credential type is described in the `credential_type` attribute of the events. The new event types are supported by the Email Event Listener.
The following event types are now deprecated and will be removed in a future version: `UPDATE_PASSWORD`, `UPDATE_PASSWORD_ERROR`, `UPDATE_TOTP`, `UPDATE_TOTP_ERROR`, `REMOVE_TOTP`, `REMOVE_TOTP_ERROR`

View file

@ -99,7 +99,7 @@ When you log in with the Admin CLI, you specify:
* A realm
* A user name
Another option is to specify a clientId only, which creates a unique service account for you to use.
Another option is to specify a clientId only, which creates a unique service account for you to use.
When you log in using a user name, use a password for the specified user. When you log in using a clientId, you need the client secret only, not the user password. You can also use the `Signed JWT` rather than the client secret.
@ -119,7 +119,7 @@ The second mechanism authenticates each command invocation for the duration of t
For example, when performing an operation, specify all the information required for authentication.
[options="nowrap",subs="attributes+"]
----
$ kcadm.sh get realms --no-config --server http://localhost:8080{kc_base_path} --realm master --user admin
$ kcadm.sh get realms --no-config --server http://localhost:8080{kc_base_path} --realm master --user admin
----
Run the `kcadm.sh help` command for more information on using the Admin CLI.
@ -131,9 +131,9 @@ If you do not specify the --password option (it is generally recommended to not
[[_working_with_alternative_configurations]]
=== Working with alternative configurations
By default, the Admin CLI maintains a configuration file named `kcadm.config`. {project_name} places this file in the user's home directory.
In Linux-based systems, the full pathname is `$HOME/.keycloak/kcadm.config`.
In Windows, the full pathname is `%HOMEPATH%\.keycloak\kcadm.config`.
By default, the Admin CLI maintains a configuration file named `kcadm.config`. {project_name} places this file in the user's home directory.
In Linux-based systems, the full pathname is `$HOME/.keycloak/kcadm.config`.
In Windows, the full pathname is `%HOMEPATH%\.keycloak\kcadm.config`.
You can use the `--config` option to point to a different file or location so you can maintain multiple authenticated sessions in parallel.
@ -199,7 +199,7 @@ The `create` and `update` commands send a JSON body to the server. You can use `
[NOTE]
====
The value in name=value pairs used in --set, -s options, are assumed to be JSON. If it cannot be parsed as valid JSON, then it will be sent to the server as a text value.
The value in name=value pairs used in --set, -s options, are assumed to be JSON. If it cannot be parsed as valid JSON, then it will be sent to the server as a text value.
If the value is enclosed in quotes after shell processing, but is not valid JSON, the quotes will be stripped and the rest of the value will be sent as text. This behavior is deprecated, please consider specifying your value without quotes or a valid JSON string literal with double quotes.
====
@ -492,13 +492,13 @@ You can turn on storage for all available ERROR events, not including auditing e
+
[options="nowrap"]
----
$ kcadm.sh update events/config -r demorealm -s eventsEnabled=true -s 'enabledEventTypes=["LOGIN_ERROR","REGISTER_ERROR","LOGOUT_ERROR","CODE_TO_TOKEN_ERROR","CLIENT_LOGIN_ERROR","FEDERATED_IDENTITY_LINK_ERROR","REMOVE_FEDERATED_IDENTITY_ERROR","UPDATE_EMAIL_ERROR","UPDATE_PROFILE_ERROR","UPDATE_PASSWORD_ERROR","UPDATE_TOTP_ERROR","VERIFY_EMAIL_ERROR","REMOVE_TOTP_ERROR","SEND_VERIFY_EMAIL_ERROR","SEND_RESET_PASSWORD_ERROR","SEND_IDENTITY_PROVIDER_LINK_ERROR","RESET_PASSWORD_ERROR","IDENTITY_PROVIDER_FIRST_LOGIN_ERROR","IDENTITY_PROVIDER_POST_LOGIN_ERROR","CUSTOM_REQUIRED_ACTION_ERROR","EXECUTE_ACTIONS_ERROR","CLIENT_REGISTER_ERROR","CLIENT_UPDATE_ERROR","CLIENT_DELETE_ERROR"]' -s eventsExpiration=172800
$ kcadm.sh update events/config -r demorealm -s eventsEnabled=true -s 'enabledEventTypes=["LOGIN_ERROR","REGISTER_ERROR","LOGOUT_ERROR","CODE_TO_TOKEN_ERROR","CLIENT_LOGIN_ERROR","FEDERATED_IDENTITY_LINK_ERROR","REMOVE_FEDERATED_IDENTITY_ERROR","UPDATE_EMAIL_ERROR","UPDATE_PROFILE_ERROR","UPDATE_PASSWORD_ERROR","UPDATE_TOTP_ERROR","UPDATE_CREDENTIAL_ERROR","VERIFY_EMAIL_ERROR","REMOVE_TOTP_ERROR","REMOVE_CREDENTIAL_ERROR","SEND_VERIFY_EMAIL_ERROR","SEND_RESET_PASSWORD_ERROR","SEND_IDENTITY_PROVIDER_LINK_ERROR","RESET_PASSWORD_ERROR","IDENTITY_PROVIDER_FIRST_LOGIN_ERROR","IDENTITY_PROVIDER_POST_LOGIN_ERROR","CUSTOM_REQUIRED_ACTION_ERROR","EXECUTE_ACTIONS_ERROR","CLIENT_REGISTER_ERROR","CLIENT_UPDATE_ERROR","CLIENT_DELETE_ERROR"]' -s eventsExpiration=172800
----
* Windows:
+
[options="nowrap"]
----
c:\> kcadm update events/config -r demorealm -s eventsEnabled=true -s "enabledEventTypes=[\"LOGIN_ERROR\",\"REGISTER_ERROR\",\"LOGOUT_ERROR\",\"CODE_TO_TOKEN_ERROR\",\"CLIENT_LOGIN_ERROR\",\"FEDERATED_IDENTITY_LINK_ERROR\",\"REMOVE_FEDERATED_IDENTITY_ERROR\",\"UPDATE_EMAIL_ERROR\",\"UPDATE_PROFILE_ERROR\",\"UPDATE_PASSWORD_ERROR\",\"UPDATE_TOTP_ERROR\",\"VERIFY_EMAIL_ERROR\",\"REMOVE_TOTP_ERROR\",\"SEND_VERIFY_EMAIL_ERROR\",\"SEND_RESET_PASSWORD_ERROR\",\"SEND_IDENTITY_PROVIDER_LINK_ERROR\",\"RESET_PASSWORD_ERROR\",\"IDENTITY_PROVIDER_FIRST_LOGIN_ERROR\",\"IDENTITY_PROVIDER_POST_LOGIN_ERROR\",\"CUSTOM_REQUIRED_ACTION_ERROR\",\"EXECUTE_ACTIONS_ERROR\",\"CLIENT_REGISTER_ERROR\",\"CLIENT_UPDATE_ERROR\",\"CLIENT_DELETE_ERROR\"]" -s eventsExpiration=172800
c:\> kcadm update events/config -r demorealm -s eventsEnabled=true -s "enabledEventTypes=[\"LOGIN_ERROR\",\"REGISTER_ERROR\",\"LOGOUT_ERROR\",\"CODE_TO_TOKEN_ERROR\",\"CLIENT_LOGIN_ERROR\",\"FEDERATED_IDENTITY_LINK_ERROR\",\"REMOVE_FEDERATED_IDENTITY_ERROR\",\"UPDATE_EMAIL_ERROR\",\"UPDATE_PROFILE_ERROR\",\"UPDATE_PASSWORD_ERROR\",\"UPDATE_TOTP_ERROR\",\"UPDATE_CREDENTIAL_ERROR\",\"VERIFY_EMAIL_ERROR\",\"REMOVE_TOTP_ERROR\",\"REMOVE_CREDENTIAL_ERROR\",\"SEND_VERIFY_EMAIL_ERROR\",\"SEND_RESET_PASSWORD_ERROR\",\"SEND_IDENTITY_PROVIDER_LINK_ERROR\",\"RESET_PASSWORD_ERROR\",\"IDENTITY_PROVIDER_FIRST_LOGIN_ERROR\",\"IDENTITY_PROVIDER_POST_LOGIN_ERROR\",\"CUSTOM_REQUIRED_ACTION_ERROR\",\"EXECUTE_ACTIONS_ERROR\",\"CLIENT_REGISTER_ERROR\",\"CLIENT_UPDATE_ERROR\",\"CLIENT_DELETE_ERROR\"]" -s eventsExpiration=172800
----
You can reset stored event types to *all available event types*. Setting the value to an empty list is the same as enumerating all.
@ -1218,7 +1218,7 @@ $ kcadm.sh remove-roles -r demorealm --uusername testuser --cclientid realm-mana
[discrete]
==== Listing a user's sessions
. Identify the user's ID,
. Identify the user's ID,
. Use the ID to compose an endpoint URI, such as `users/ID/sessions`.
. Use the `get` command to retrieve a list of the user's sessions.
+
@ -1406,7 +1406,7 @@ $ kcadm.sh get-roles -r demorealm --gname Group --available
Use the `get-roles` command to list assigned, available, and effective client roles for a group.
. Specify the target group by name (`--gname` option) or ID (`--gid` option),
. Specify the target group by name (`--gname` option) or ID (`--gid` option),
. Specify the client by the clientId attribute (`--cclientid` option) or ID (`--id` option) to list *assigned* client roles for the user.
+
For example:
@ -1895,7 +1895,7 @@ $ kcadm get "authentication/config/dd91611a-d25c-421a-87e2-227c18421833" -r demo
==== Updating configuration for an execution
. Get the execution for the flow.
. Get the flow's `authenticationConfig` attribute.
. Get the flow's `authenticationConfig` attribute.
. Note the config ID from the attribute.
. Run the `update` command on the `authentication/config/ID` endpoint.

View file

@ -123,15 +123,21 @@ image:images/search-user-event.png[Search user event]
|Send Password Reset
|{project_name} sends a password reset email.
|Update Password
|Update Password (deprecated)
|The password for an account changes.
|Update TOTP
|Update Credential
|The password or (time-based) one-time Password (OTP/TOTP) settings for an account changes.
|Update TOTP (deprecated)
|The Time-based One-time Password (TOTP) settings for an account changes.
|Remove TOTP
|Remove TOTP (deprecated)
|{project_name} removes TOTP from an account.
|Remove Credential
|{project_name} removes a credential from an account.
|Send Verify Email
|{project_name} sends an email verification email.
@ -191,6 +197,8 @@ The Email Event Listener sends a message to the user's email address when an eve
* Update Password.
* Update Time-based One-time Password (TOTP).
* Remove One-time Password (OTP).
* Update Credential.
* Remove Credential.
The following conditions need to be met for an email to be sent:
@ -217,6 +225,6 @@ You can exclude events by using the `--spi-events-listener-email-exclude-events`
[source,bash]
----
kc.[sh|bat] --spi-events-listener-email-exclude-events=UPDATE_TOTP,REMOVE_TOTP
kc.[sh|bat] --spi-events-listener-email-exclude-events=UPDATE_CREDENTIAL,REMOVE_CREDENTIAL
----

View file

@ -242,3 +242,10 @@ As part of this change the following related configuration options for the SPI h
- `--spi-login-protocol-openid-connect-suppress-logout-confirmation-screen`
If you were still making use these options or the `redirect_uri` parameter for logout you should implement the link:https://openid.net/specs/openid-connect-rpinitiated-1_0.html[OpenID Connect RP-Initiated Logout specification] instead.
= New generalized event types for credentials
There are now generalized events for updating (`UPDATE_CREDENTIAL`) and removing (`REMOVE_CREDENTIAL`) a credential. The credential type is described in the `credential_type` attribute of the events.
The new event types are supported by the Email Event Listener.
The following event types are now deprecated and will be removed in a future version: `UPDATE_PASSWORD`, `UPDATE_PASSWORD_ERROR`, `UPDATE_TOTP`, `UPDATE_TOTP_ERROR`, `REMOVE_TOTP`, `REMOVE_TOTP_ERROR`

View file

@ -2134,7 +2134,7 @@
"smtpServer" : { },
"eventsEnabled" : false,
"eventsListeners" : [ "jboss-logging" ],
"enabledEventTypes" : [ "SEND_RESET_PASSWORD", "SEND_VERIFY_EMAIL", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "UPDATE_TOTP", "REMOVE_TOTP", "REVOKE_GRANT", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "UPDATE_EMAIL", "REGISTER_ERROR", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "UPDATE_PROFILE_ERROR", "IMPERSONATE", "LOGIN", "UPDATE_PASSWORD_ERROR", "CLIENT_UPDATE_ERROR", "UPDATE_PROFILE", "REGISTER", "LOGOUT", "FEDERATED_IDENTITY_LINK", "CLIENT_REGISTER_ERROR", "CLIENT_REGISTER", "SEND_VERIFY_EMAIL_ERROR", "UPDATE_PASSWORD", "RESET_PASSWORD", "FEDERATED_IDENTITY_LINK_ERROR", "CLIENT_DELETE", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "VERIFY_EMAIL", "SEND_RESET_PASSWORD_ERROR", "CLIENT_DELETE_ERROR", "CLIENT_LOGIN_ERROR", "CLIENT_UPDATE", "REMOVE_FEDERATED_IDENTITY_ERROR", "EXECUTE_ACTIONS", "CUSTOM_REQUIRED_ACTION_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN" ],
"enabledEventTypes" : [ "SEND_RESET_PASSWORD", "SEND_VERIFY_EMAIL", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "REMOVE_CREDENTIAL", "UPDATE_TOTP", "REMOVE_TOTP", "REVOKE_GRANT", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "UPDATE_EMAIL", "REGISTER_ERROR", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "UPDATE_PROFILE_ERROR", "IMPERSONATE", "LOGIN", "UPDATE_PASSWORD_ERROR", "UPDATE_CREDENTIAL_ERROR", "CLIENT_UPDATE_ERROR", "UPDATE_PROFILE", "REGISTER", "LOGOUT", "FEDERATED_IDENTITY_LINK", "CLIENT_REGISTER_ERROR", "CLIENT_REGISTER", "SEND_VERIFY_EMAIL_ERROR", "UPDATE_CREDENTIAL", "UPDATE_PASSWORD" ,"RESET_PASSWORD", "FEDERATED_IDENTITY_LINK_ERROR", "CLIENT_DELETE", "REMOVE_CREDENTIAL_ERROR", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "VERIFY_EMAIL", "SEND_RESET_PASSWORD_ERROR", "CLIENT_DELETE_ERROR", "CLIENT_LOGIN_ERROR", "CLIENT_UPDATE", "REMOVE_FEDERATED_IDENTITY_ERROR", "EXECUTE_ACTIONS", "CUSTOM_REQUIRED_ACTION_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN" ],
"adminEventsEnabled" : true,
"adminEventsDetailsEnabled" : true,
"identityProviders" : [ {
@ -4210,7 +4210,7 @@
"smtpServer" : { },
"eventsEnabled" : false,
"eventsListeners" : [ "jboss-logging" ],
"enabledEventTypes" : [ "SEND_RESET_PASSWORD", "SEND_VERIFY_EMAIL", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "UPDATE_TOTP", "REMOVE_TOTP", "REVOKE_GRANT", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "UPDATE_EMAIL", "REGISTER_ERROR", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "UPDATE_PROFILE_ERROR", "IMPERSONATE", "LOGIN", "UPDATE_PASSWORD_ERROR", "CLIENT_UPDATE_ERROR", "UPDATE_PROFILE", "REGISTER", "LOGOUT", "FEDERATED_IDENTITY_LINK", "CLIENT_REGISTER_ERROR", "CLIENT_REGISTER", "SEND_VERIFY_EMAIL_ERROR", "UPDATE_PASSWORD", "RESET_PASSWORD", "FEDERATED_IDENTITY_LINK_ERROR", "CLIENT_DELETE", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "VERIFY_EMAIL", "SEND_RESET_PASSWORD_ERROR", "CLIENT_DELETE_ERROR", "CLIENT_LOGIN_ERROR", "CLIENT_UPDATE", "REMOVE_FEDERATED_IDENTITY_ERROR", "EXECUTE_ACTIONS", "CUSTOM_REQUIRED_ACTION_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN" ],
"enabledEventTypes" : [ "SEND_RESET_PASSWORD", "SEND_VERIFY_EMAIL", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "REMOVE_CREDENTIAL", "UPDATE_TOTP", "REMOVE_TOTP", "REVOKE_GRANT", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "UPDATE_EMAIL", "REGISTER_ERROR", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "UPDATE_PROFILE_ERROR", "IMPERSONATE", "LOGIN", "UPDATE_PASSWORD_ERROR", "UPDATE_CREDENTIAL_ERROR", "CLIENT_UPDATE_ERROR", "UPDATE_PROFILE", "REGISTER", "LOGOUT", "FEDERATED_IDENTITY_LINK", "CLIENT_REGISTER_ERROR", "CLIENT_REGISTER", "SEND_VERIFY_EMAIL_ERROR", "UPDATE_CREDENTIAL", "UPDATE_PASSWORD", "RESET_PASSWORD", "FEDERATED_IDENTITY_LINK_ERROR", "CLIENT_DELETE", "REMOVE_CREDENTIAL_ERROR", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "VERIFY_EMAIL", "SEND_RESET_PASSWORD_ERROR", "CLIENT_DELETE_ERROR", "CLIENT_LOGIN_ERROR", "CLIENT_UPDATE", "REMOVE_FEDERATED_IDENTITY_ERROR", "EXECUTE_ACTIONS", "CUSTOM_REQUIRED_ACTION_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN" ],
"adminEventsEnabled" : true,
"adminEventsDetailsEnabled" : true,
"identityProviders" : [ {

View file

@ -3241,4 +3241,12 @@ temporaryService=Temporary admin service account. Ensure it is replaced with a p
addOrganizationAttributes.label=Add organization attributes
addOrganizationAttributes.help=If enabled, the organization attributes will be available for each organization mapped to the token.
disableConfirmUserTitle=Disable user?
disableConfirmUser=Are you sure you want to disable this user?
disableConfirmUser=Are you sure you want to disable this user?
eventTypes.UPDATE_CREDENTIAL.name=Update credential
eventTypes.UPDATE_CREDENTIAL.description=Update credential
eventTypes.UPDATE_CREDENTIAL_ERROR.name=Update credential error
eventTypes.UPDATE_CREDENTIAL_ERROR.description=Update credential error
eventTypes.REMOVE_CREDENTIAL.name=Remove credential
eventTypes.REMOVE_CREDENTIAL.description=Remove credential
eventTypes.REMOVE_CREDENTIAL_ERROR.name=Remove credential error
eventTypes.REMOVE_CREDENTIAL_ERROR.description=Remove credential error

View file

@ -7212,6 +7212,8 @@
"REMOVE_FEDERATED_IDENTITY_ERROR",
"REMOVE_TOTP",
"REMOVE_TOTP_ERROR",
"REMOVE_CREDENTIAL",
"REMOVE_CREDENTIAL_ERROR",
"RESET_PASSWORD",
"RESET_PASSWORD_ERROR",
"RESTART_AUTHENTICATION",
@ -7234,6 +7236,8 @@
"UPDATE_EMAIL_ERROR",
"UPDATE_PASSWORD",
"UPDATE_PASSWORD_ERROR",
"UPDATE_CREDENTIAL",
"UPDATE_CREDENTIAL_ERROR",
"UPDATE_PROFILE",
"UPDATE_PROFILE_ERROR",
"UPDATE_TOTP",

View file

@ -7074,6 +7074,8 @@
"REMOVE_FEDERATED_IDENTITY_ERROR",
"REMOVE_TOTP",
"REMOVE_TOTP_ERROR",
"REMOVE_CREDENTIAL",
"REMOVE_CREDENTIAL_ERROR",
"RESET_PASSWORD",
"RESET_PASSWORD_ERROR",
"RESTART_AUTHENTICATION",
@ -7096,6 +7098,8 @@
"UPDATE_EMAIL_ERROR",
"UPDATE_PASSWORD",
"UPDATE_PASSWORD_ERROR",
"UPDATE_CREDENTIAL",
"UPDATE_CREDENTIAL_ERROR",
"UPDATE_PROFILE",
"UPDATE_PROFILE_ERROR",
"UPDATE_TOTP",

View file

@ -84,6 +84,10 @@ type EventType =
| "TOKEN_EXCHANGE"
| "TOKEN_EXCHANGE_ERROR"
| "PERMISSION_TOKEN"
| "PERMISSION_TOKEN_ERROR";
| "PERMISSION_TOKEN_ERROR"
| "UPDATE_CREDENTIAL"
| "UPDATE_CREDENTIAL_ERROR"
| "REMOVE_CREDENTIAL"
| "REMOVE_CREDENTIAL_ERROR";
export default EventType;

View file

@ -62,16 +62,24 @@ public enum EventType implements EnumWithStableIndex {
UPDATE_EMAIL_ERROR(0x10000 + UPDATE_EMAIL.getStableIndex(), true),
UPDATE_PROFILE(11, true),
UPDATE_PROFILE_ERROR(0x10000 + UPDATE_PROFILE.getStableIndex(), true),
@Deprecated
UPDATE_PASSWORD(12, true),
@Deprecated
UPDATE_PASSWORD_ERROR(0x10000 + UPDATE_PASSWORD.getStableIndex(), true),
@Deprecated
UPDATE_TOTP(13, true),
@Deprecated
UPDATE_TOTP_ERROR(0x10000 + UPDATE_TOTP.getStableIndex(), true),
VERIFY_EMAIL(14, true),
VERIFY_EMAIL_ERROR(0x10000 + VERIFY_EMAIL.getStableIndex(), true),
VERIFY_PROFILE(15, true),
VERIFY_PROFILE_ERROR(0x10000 + VERIFY_PROFILE.getStableIndex(), true),
@Deprecated
REMOVE_TOTP(16, true),
@Deprecated
REMOVE_TOTP_ERROR(0x10000 + REMOVE_TOTP.getStableIndex(), true),
GRANT_CONSENT(17, true),
@ -169,6 +177,13 @@ public enum EventType implements EnumWithStableIndex {
FEDERATED_IDENTITY_OVERRIDE_LINK(55, true),
FEDERATED_IDENTITY_OVERRIDE_LINK_ERROR(0x10000 + FEDERATED_IDENTITY_OVERRIDE_LINK.getStableIndex(), true),
UPDATE_CREDENTIAL(56, true),
UPDATE_CREDENTIAL_ERROR(0x10000 + UPDATE_CREDENTIAL.getStableIndex(), true),
REMOVE_CREDENTIAL(57, true),
REMOVE_CREDENTIAL_ERROR(0x10000 + REMOVE_CREDENTIAL.getStableIndex(), true),
INVITE_ORG(60, true),
INVITE_ORG_ERROR(0x10000 + INVITE_ORG.getStableIndex(), true);

View file

@ -143,28 +143,31 @@ public class DeleteCredentialAction implements RequiredActionProvider, RequiredA
context.challenge(challenge);
}
private void setupEvent(CredentialModel credential, EventBuilder event) {
if (credential != null) {
if (OTPCredentialModel.TYPE.equals(credential.getType())) {
event.event(EventType.REMOVE_TOTP);
}
event.detail(Details.CREDENTIAL_TYPE, credential.getType())
.detail(Details.CREDENTIAL_ID, credential.getId())
.detail(Details.CREDENTIAL_USER_LABEL, credential.getUserLabel());
}
}
@Override
public void processAction(RequiredActionContext context) {
EventBuilder event = context.getEvent();
event.event(EventType.REMOVE_CREDENTIAL);
EventBuilder deprecatedEvent = null;
String credentialId = context.getAuthenticationSession().getClientNote(Constants.KC_ACTION_PARAMETER);
CredentialModel credential = context.getUser().credentialManager().getStoredCredentialById(credentialId);
setupEvent(credential, event);
if (credential != null) {
event
.detail(Details.CREDENTIAL_TYPE, credential.getType())
.detail(Details.CREDENTIAL_ID, credential.getId())
.detail(Details.CREDENTIAL_USER_LABEL, credential.getUserLabel());
if (OTPCredentialModel.TYPE.equals(credential.getType())) {
deprecatedEvent = event.clone().event(EventType.REMOVE_TOTP);
}
}
try {
CredentialDeleteHelper.removeCredential(context.getSession(), context.getUser(), credentialId, () -> getCurrentLoa(context.getSession(), context.getAuthenticationSession()));
context.success();
if (deprecatedEvent != null) {
deprecatedEvent.success();
}
} catch (WebApplicationException wae) {
Response response = context.getSession().getProvider(LoginFormsProvider.class)
@ -174,6 +177,10 @@ public class DeleteCredentialAction implements RequiredActionProvider, RequiredA
.createErrorPage(Response.Status.BAD_REQUEST);
event.detail(Details.REASON, wae.getMessage())
.error(Errors.DELETE_CREDENTIAL_FAILED);
if (deprecatedEvent != null) {
deprecatedEvent.detail(Details.REASON, wae.getMessage())
.error(Errors.DELETE_CREDENTIAL_FAILED);
}
context.challenge(response);
}
}

View file

@ -45,6 +45,7 @@ import org.keycloak.models.RequiredActionConfigModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.policy.MaxAuthAgePasswordPolicyProviderFactory;
import org.keycloak.provider.ProviderConfigProperty;
@ -147,13 +148,18 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
AuthenticationSessionModel authSession = context.getAuthenticationSession();
UserModel user = context.getUser();
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
event.event(EventType.UPDATE_PASSWORD);
event.event(EventType.UPDATE_CREDENTIAL);
event.detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.PASSWORD);
EventBuilder deprecatedEvent = event.clone().event(EventType.UPDATE_PASSWORD);
String passwordNew = formData.getFirst("password-new");
String passwordConfirm = formData.getFirst("password-confirm");
EventBuilder errorEvent = event.clone().event(EventType.UPDATE_PASSWORD_ERROR)
EventBuilder errorEvent = event.clone().event(EventType.UPDATE_CREDENTIAL_ERROR)
.client(authSession.getClient())
.user(authSession.getAuthenticatedUser());
EventBuilder deprecatedErrorEvent = errorEvent.clone().event(EventType.UPDATE_PASSWORD_ERROR);
if (Validation.isBlank(passwordNew)) {
Response challenge = context.form()
@ -162,6 +168,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
context.challenge(challenge);
errorEvent.error(Errors.PASSWORD_MISSING);
deprecatedErrorEvent.error(Errors.PASSWORD_MISSING);
return;
} else if (!passwordNew.equals(passwordConfirm)) {
Response challenge = context.form()
@ -170,6 +177,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
context.challenge(challenge);
errorEvent.error(Errors.PASSWORD_CONFIRM_ERROR);
deprecatedErrorEvent.error(Errors.PASSWORD_CONFIRM_ERROR);
return;
}
@ -180,8 +188,10 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
try {
user.credentialManager().updateCredential(UserCredentialModel.password(passwordNew, false));
context.success();
deprecatedEvent.success();
} catch (ModelException me) {
errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
deprecatedErrorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
Response challenge = context.form()
.setAttribute("username", authSession.getAuthenticatedUser().getUsername())
.setError(me.getMessage(), me.getParameters())
@ -190,6 +200,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
return;
} catch (Exception ape) {
errorEvent.detail(Details.REASON, ape.getMessage()).error(Errors.PASSWORD_REJECTED);
deprecatedErrorEvent.detail(Details.REASON, ape.getMessage()).error(Errors.PASSWORD_REJECTED);
Response challenge = context.form()
.setAttribute("username", authSession.getAuthenticatedUser().getUsername())
.setError(ape.getMessage())

View file

@ -27,6 +27,7 @@ import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.credential.OTPCredentialProvider;
import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.KeycloakSession;
@ -55,7 +56,7 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
public InitiatedActionSupport initiatedActionSupport() {
return InitiatedActionSupport.SUPPORTED;
}
@Override
public void evaluateTriggers(RequiredActionContext context) {
}
@ -71,7 +72,7 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
@Override
public void processAction(RequiredActionContext context) {
EventBuilder event = context.getEvent();
event.event(EventType.UPDATE_TOTP);
event.event(EventType.UPDATE_CREDENTIAL);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String challengeResponse = formData.getFirst("totp");
String totpSecret = formData.getFirst("totpSecret");
@ -80,6 +81,9 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
OTPPolicy policy = context.getRealm().getOTPPolicy();
OTPCredentialModel credentialModel = OTPCredentialModel.createFromPolicy(context.getRealm(), totpSecret, userLabel);
event.detail(Details.CREDENTIAL_TYPE, credentialModel.getType());
EventBuilder deprecatedEvent = event.clone().event(EventType.UPDATE_TOTP);
if (Validation.isBlank(challengeResponse)) {
Response challenge = context.form()
.setAttribute("mode", mode)
@ -122,6 +126,7 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
}
context.getAuthenticationSession().removeAuthNote(Constants.TOTP_SECRET_KEY);
context.success();
deprecatedEvent.success();
}

View file

@ -33,6 +33,8 @@ import jakarta.ws.rs.core.Response;
import com.webauthn4j.WebAuthnRegistrationManager;
import com.webauthn4j.data.AuthenticatorTransport;
import org.jboss.logging.Logger;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.http.HttpRequest;
import org.keycloak.WebAuthnConstants;
import org.keycloak.authentication.AuthenticatorUtil;
@ -184,6 +186,15 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
return WebAuthnCredentialProviderFactory.PROVIDER_ID;
}
/**
* Method to provide removal and deprecation hint
* @deprecated For compatibility sake as long as we use @link {@link EventType#UPDATE_PASSWORD} , {@link EventType#UPDATE_TOTP} a.s.o.
*/
@Deprecated
protected EventType getOriginalEventTypeForBackwardsCompatibility(RequiredActionContext context) {
return context.getEvent().getEvent().getType();
}
@Override
public void processAction(RequiredActionContext context) {
@ -195,12 +206,15 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
return;
}
context.getEvent().detail(Details.CREDENTIAL_TYPE, getCredentialType());
EventType originalEventType = getOriginalEventTypeForBackwardsCompatibility(context);
context.getEvent()
.event(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, getCredentialType());
// receive error from navigator.credentials.create()
String errorMsgFromWebAuthnApi = params.getFirst(WebAuthnConstants.ERROR);
if (errorMsgFromWebAuthnApi != null && !errorMsgFromWebAuthnApi.isEmpty()) {
setErrorResponse(context, WEBAUTHN_ERROR_REGISTER_VERIFICATION, errorMsgFromWebAuthnApi);
setErrorResponse(context, WEBAUTHN_ERROR_REGISTER_VERIFICATION, errorMsgFromWebAuthnApi, originalEventType);
return;
}
@ -269,14 +283,15 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
.detail(WebAuthnConstants.PUBKEY_CRED_ID_ATTR, publicKeyCredentialId)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, label)
.detail(WebAuthnConstants.PUBKEY_CRED_AAGUID_ATTR, aaguid);
context.getEvent().clone().event(originalEventType).success();
context.success();
} catch (WebAuthnException wae) {
if (logger.isDebugEnabled()) logger.debug(wae.getMessage(), wae);
setErrorResponse(context, WEBAUTHN_ERROR_REGISTRATION, wae.getMessage());
setErrorResponse(context, WEBAUTHN_ERROR_REGISTRATION, wae.getMessage(), originalEventType);
return;
} catch (Exception e) {
if (logger.isDebugEnabled()) logger.debug(e.getMessage(), e);
setErrorResponse(context, WEBAUTHN_ERROR_REGISTRATION, e.getMessage());
setErrorResponse(context, WEBAUTHN_ERROR_REGISTRATION, e.getMessage(), originalEventType);
return;
}
}
@ -387,15 +402,17 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
// NOP
}
private void setErrorResponse(RequiredActionContext context, final String errorCase, final String errorMessage) {
private void setErrorResponse(RequiredActionContext context, final String errorCase, final String errorMessage, @Deprecated final EventType originalEventType) {
Response errorResponse = null;
switch (errorCase) {
case WEBAUTHN_ERROR_REGISTER_VERIFICATION:
logger.warnv("WebAuthn API .create() response validation failure. {0}", errorMessage);
context.getEvent()
EventBuilder registerVerificationEvent = context.getEvent()
.detail(REG_ERR_LABEL, errorCase)
.detail(REG_ERR_DETAIL_LABEL, errorMessage)
.error(Errors.INVALID_USER_CREDENTIALS);
.detail(REG_ERR_DETAIL_LABEL, errorMessage);
EventBuilder deprecatedRegisterVerificationEvent = registerVerificationEvent.clone().event(originalEventType);
registerVerificationEvent.error(Errors.INVALID_USER_CREDENTIALS);
deprecatedRegisterVerificationEvent.error(Errors.INVALID_USER_CREDENTIALS);
errorResponse = context.form()
.setError(errorCase, errorMessage)
.setAttribute(WEB_AUTHN_TITLE_ATTR, WEBAUTHN_REGISTER_TITLE)
@ -404,10 +421,12 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
break;
case WEBAUTHN_ERROR_REGISTRATION:
logger.warn(errorCase);
context.getEvent()
.detail(REG_ERR_LABEL, errorCase)
.detail(REG_ERR_DETAIL_LABEL, errorMessage)
.error(Errors.INVALID_REGISTRATION);
EventBuilder registrationEvent = context.getEvent()
.detail(REG_ERR_LABEL, errorCase)
.detail(REG_ERR_DETAIL_LABEL, errorMessage);
EventBuilder deprecatedRegistrationEvent = registrationEvent.clone().event(originalEventType);
deprecatedRegistrationEvent.error(Errors.INVALID_REGISTRATION);
registrationEvent.error(Errors.INVALID_REGISTRATION);
errorResponse = context.form()
.setError(errorCase, errorMessage)
.setAttribute(WEB_AUTHN_TITLE_ATTR, WEBAUTHN_REGISTER_TITLE)

View file

@ -44,7 +44,7 @@ public class EmailEventListenerProviderFactory implements EventListenerProviderF
public static final String ID = "email";
static {
Collections.addAll(SUPPORTED_EVENTS, EventType.LOGIN_ERROR, EventType.UPDATE_PASSWORD, EventType.REMOVE_TOTP, EventType.UPDATE_TOTP);
Collections.addAll(SUPPORTED_EVENTS, EventType.LOGIN_ERROR, EventType.UPDATE_PASSWORD, EventType.REMOVE_TOTP, EventType.UPDATE_TOTP, EventType.UPDATE_CREDENTIAL, EventType.REMOVE_CREDENTIAL);
}
private Set<EventType> includedEvents = new HashSet<>();

View file

@ -307,10 +307,14 @@ public class AccountCredentialResource {
realm.getName());
CredentialModel credential = CredentialDeleteHelper.removeCredential(session, user, credentialId, this::getCurrentAuthenticatedLevel);
if (credential != null && OTPCredentialModel.TYPE.equals(credential.getType())) {
event.event(EventType.REMOVE_TOTP)
if (credential != null) {
event.event(EventType.REMOVE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, credential.getType())
.detail(Details.SELECTED_CREDENTIAL_ID, credentialId)
.detail(Details.CREDENTIAL_USER_LABEL, credential.getUserLabel());
if (OTPCredentialModel.TYPE.equals(credential.getType())) {
event.clone().event(EventType.REMOVE_TOTP).success();
}
event.success();
}
}

View file

@ -29,7 +29,7 @@ public class Constants {
public static final EventType[] EXPOSED_LOG_EVENTS = {
EventType.LOGIN, EventType.LOGOUT, EventType.REGISTER, EventType.REMOVE_FEDERATED_IDENTITY, EventType.REMOVE_TOTP, EventType.SEND_RESET_PASSWORD,
EventType.SEND_VERIFY_EMAIL, EventType.FEDERATED_IDENTITY_LINK, EventType.UPDATE_EMAIL, EventType.UPDATE_PASSWORD, EventType.UPDATE_PROFILE, EventType.UPDATE_TOTP, EventType.VERIFY_EMAIL
EventType.SEND_VERIFY_EMAIL, EventType.FEDERATED_IDENTITY_LINK, EventType.UPDATE_EMAIL, EventType.UPDATE_CREDENTIAL, EventType.REMOVE_CREDENTIAL, EventType.UPDATE_PROFILE, EventType.UPDATE_PASSWORD, EventType.UPDATE_PROFILE, EventType.UPDATE_TOTP, EventType.VERIFY_EMAIL
};
public static final Set<String> EXPOSED_LOG_DETAILS = new HashSet<>();

View file

@ -107,7 +107,7 @@ import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AccountRestServiceTest extends AbstractRestServiceTest {
@Rule
public AssertEvents events = new AssertEvents(this);
@ -227,7 +227,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
UserRepresentation user = getUser(false);
assertNull(user.getUserProfileMetadata());
}
protected static UserProfileAttributeMetadata getUserProfileAttributeMetadata(UserRepresentation user, String attName) {
if(user.getUserProfileMetadata() == null)
return null;
@ -238,7 +238,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
}
return null;
}
protected static UserProfileAttributeMetadata assertUserProfileAttributeMetadata(UserRepresentation user, String attName, String displayName, boolean required, boolean readOnly) {
UserProfileAttributeMetadata uam = getUserProfileAttributeMetadata(user, attName);
@ -310,7 +310,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
}
}
/**
* Reproducer for bugs KEYCLOAK-17424 and KEYCLOAK-17582
*/
@ -323,13 +323,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setRegistrationEmailAsUsername(false);
adminClient.realm("test").update(realmRep);
//set flag over adminClient to initial value
UserResource userResource = adminClient.realm("test").users().get(user.getId());
org.keycloak.representations.idm.UserRepresentation ur = userResource.toRepresentation();
ur.setEmailVerified(true);
userResource.update(ur);
//make sure flag is correct before the test
//make sure flag is correct before the test
user = getUser();
assertEquals(true, user.isEmailVerified());
@ -339,7 +339,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
assertEquals(originalEmail, user.getEmail());
assertEquals(true, user.isEmailVerified());
// Update email - flag must be reset to false
user.setEmail("bobby@localhost");
user = updateAndGet(user);
@ -402,7 +402,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
.detail(Details.UPDATED_LAST_NAME, "Simpsons")
.assertEvent();
events.assertEmpty();
} finally {
RealmRepresentation realmRep = adminClient.realm("test").toRepresentation();
realmRep.setEditUsernameAllowed(true);
@ -417,7 +417,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
assertEquals(204, response.getStatus());
}
}
@Test
public void testUpdateProfile() throws IOException {
String userProfileCfg = "{\"attributes\": ["
@ -455,7 +455,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
assertEquals("val1", user.getAttributes().get("attr1").get(0));
assertEquals(1, user.getAttributes().get("attr2").size());
assertEquals("val2", user.getAttributes().get("attr2").get(0));
// Update attributes
user.getAttributes().remove("attr1");
user.getAttributes().get("attr2").add("val3");
@ -637,7 +637,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
throw e;
}
}
protected UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
SimpleHttp a = SimpleHttpDefault.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user);
try {
@ -886,6 +886,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
.user(user.toRepresentation().getId())
.detail(Details.SELECTED_CREDENTIAL_ID, otpCredential.getId())
.detail(Details.CREDENTIAL_USER_LABEL, "totpCredentialUserLabel")
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.assertEvent();
events.expect(EventType.REMOVE_CREDENTIAL)
.client("account")
.user(user.toRepresentation().getId())
.detail(Details.SELECTED_CREDENTIAL_ID, otpCredential.getId())
.detail(Details.CREDENTIAL_USER_LABEL, "totpCredentialUserLabel")
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.assertEvent();
events.assertEmpty();
}

View file

@ -91,7 +91,7 @@ public class AppInitiatedActionDeleteCredentialTest extends AbstractAppInitiated
}
@Test
public void removeTotpSuccess() throws Exception {
public void removeOtpSuccess() throws Exception {
String credentialId = getCredentialIdByType(OTPCredentialModel.TYPE);
oauth.kcAction(getKcActionParamForDeleteCredential(credentialId));
@ -113,10 +113,16 @@ public class AppInitiatedActionDeleteCredentialTest extends AbstractAppInitiated
.detail(Details.CREDENTIAL_ID, credentialId)
.detail(Details.CUSTOM_REQUIRED_ACTION, DeleteCredentialAction.PROVIDER_ID)
.assertEvent();
events.expect(EventType.REMOVE_CREDENTIAL)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.CREDENTIAL_ID, credentialId)
.detail(Details.CUSTOM_REQUIRED_ACTION, DeleteCredentialAction.PROVIDER_ID)
.assertEvent();
}
@Test
public void removeTotpCancel() throws Exception {
public void removeOtpCancel() throws Exception {
String credentialId = getCredentialIdByType(OTPCredentialModel.TYPE);
loginPasswordAndOtp();
@ -155,7 +161,7 @@ public class AppInitiatedActionDeleteCredentialTest extends AbstractAppInitiated
errorPage.assertCurrent();
events.expect(EventType.CUSTOM_REQUIRED_ACTION)
events.expect(EventType.REMOVE_CREDENTIAL)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE)
.detail(Details.CREDENTIAL_ID, credentialId)
@ -218,7 +224,7 @@ public class AppInitiatedActionDeleteCredentialTest extends AbstractAppInitiated
}
@Test
public void removeTotpCustomLabel() throws Exception {
public void removeOtpCustomLabel() throws Exception {
String credentialId = getCredentialIdByType(OTPCredentialModel.TYPE);
testRealm().users().get(userId).setCredentialUserLabel(credentialId, "custom-otp-authenticator");
@ -242,6 +248,13 @@ public class AppInitiatedActionDeleteCredentialTest extends AbstractAppInitiated
.detail(Details.CREDENTIAL_USER_LABEL, "custom-otp-authenticator")
.detail(Details.CUSTOM_REQUIRED_ACTION, DeleteCredentialAction.PROVIDER_ID)
.assertEvent();
events.expect(EventType.REMOVE_CREDENTIAL)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.CREDENTIAL_ID, credentialId)
.detail(Details.CREDENTIAL_USER_LABEL, "custom-otp-authenticator")
.detail(Details.CUSTOM_REQUIRED_ACTION, DeleteCredentialAction.PROVIDER_ID)
.assertEvent();
}
private String getCredentialIdByType(String type) {

View file

@ -28,6 +28,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -107,6 +108,7 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
});
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
assertKcActionStatus(SUCCESS);
@ -145,6 +147,7 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
changePasswordPage.changePassword("new-password", "new-password");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
assertKcActionStatus(SUCCESS);
}
@ -188,6 +191,7 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
changePasswordPage.changePassword("new-password", "new-password");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
assertKcActionStatus(SUCCESS);
} finally {
// reset password policy to previous state
@ -232,6 +236,7 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
changePasswordPage.changePassword("new-password", "new-password");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
assertKcActionStatus(SUCCESS);
}
@ -261,6 +266,7 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
changePasswordPage.changePassword("All Right Then, Keep Your Secrets", "All Right Then, Keep Your Secrets");
events.expectLogout(event2.getSessionId()).detail(Details.LOGOUT_TRIGGERED_BY_REQUIRED_ACTION, UserModel.RequiredAction.UPDATE_PASSWORD.name()).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
assertKcActionStatus(SUCCESS);
sessions = testUser.getUserSessions();
@ -289,6 +295,7 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
changePasswordPage.uncheckLogoutSessions();
changePasswordPage.changePassword("All Right Then, Keep Your Secrets", "All Right Then, Keep Your Secrets");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
assertKcActionStatus(SUCCESS);
assertEquals(2, testUser.getUserSessions().size());

View file

@ -111,12 +111,21 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
events.poll(); // skip to totp event
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent()
String authSessionId1 = events.expectRequiredAction(EventType.UPDATE_TOTP)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setuptotp").assertEvent()
.getDetails().get(Details.CODE_ID);
String authSessionId2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setuptotp").assertEvent()
.getDetails().get(Details.CODE_ID);
assertKcActionStatus(SUCCESS);
events.expectLogin().user(userId).session(authSessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
assertEquals(authSessionId1, authSessionId2);
events.expectLogin().user(userId).session(authSessionId2).detail(Details.USERNAME, "setuptotp").assertEvent();
}
@Test
@ -346,17 +355,22 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
totpPage.configure(totp.generateTOTP(totpSecret));
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
String authSessionId1 = events.expectRequiredAction(EventType.UPDATE_TOTP)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
String authSessionId2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
assertKcActionStatus(SUCCESS);
EventRepresentation loginEvent = events.expectLogin().session(authSessionId).assertEvent();
assertEquals(authSessionId1, authSessionId2);
EventRepresentation loginEvent = events.expectLogin().session(authSessionId2).assertEvent();
OAuthClient.AccessTokenResponse tokenResponse = sendTokenRequestAndGetResponse(loginEvent);
oauth.idTokenHint(tokenResponse.getIdToken()).openLogout();
events.expectLogout(authSessionId).assertEvent();
events.expectLogout(authSessionId2).assertEvent();
setOtpTimeOffset(TimeBasedOTP.DEFAULT_INTERVAL_SECONDS, totp);
@ -389,7 +403,14 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
assertKcActionStatus(SUCCESS);
events.poll();
events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
events.expectRequiredAction(EventType.UPDATE_TOTP)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setuptotp2").assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setuptotp2").assertEvent();
EventRepresentation loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
@ -448,12 +469,17 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
TimeBasedOTP timeBased = new TimeBasedOTP(HmacOTP.HMAC_SHA1, 8, 30, 1);
totpPage.configure(timeBased.generateTOTP(totpSecret));
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
String sessionId1 = events.expectRequiredAction(EventType.UPDATE_TOTP)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
String sessionId2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
assertKcActionStatus(SUCCESS);
EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
assertEquals(sessionId1, sessionId2);
EventRepresentation loginEvent = events.expectLogin().session(sessionId2).assertEvent();
OAuthClient.AccessTokenResponse tokenResponse = sendTokenRequestAndGetResponse(loginEvent);
oauth.idTokenHint(tokenResponse.getIdToken()).openLogout();
@ -503,12 +529,16 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
HmacOTP otpgen = new HmacOTP(6, HmacOTP.HMAC_SHA1, 1);
totpPage.configure(otpgen.generateHOTP(totpSecret, 0));
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
String sessionId1 = events.expectRequiredAction(EventType.UPDATE_TOTP)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
String sessionId2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
//RequestType reqType = appPage.getRequestType();
assertKcActionStatus(SUCCESS);
EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
EventRepresentation loginEvent = events.expectLogin().session(sessionId1).assertEvent();
OAuthClient.AccessTokenResponse tokenResponse = sendTokenRequestAndGetResponse(loginEvent);
oauth.idTokenHint(tokenResponse.getIdToken()).openLogout();

View file

@ -23,6 +23,8 @@ import org.junit.Test;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
@ -86,11 +88,20 @@ public class RequiredActionMultipleActionsTest extends AbstractTestRealmKeycloak
public String updatePassword(String codeId) {
changePasswordPage.changePassword("new-password", "new-password");
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_PASSWORD);
AssertEvents.ExpectedEvent expectedEvent1 = events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE);
if (codeId != null) {
expectedEvent.detail(Details.CODE_ID, codeId);
expectedEvent1.detail(Details.CODE_ID, codeId);
}
return expectedEvent.assertEvent().getDetails().get(Details.CODE_ID);
EventRepresentation eventRep1 = expectedEvent1.assertEvent();
AssertEvents.ExpectedEvent expectedEvent2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE);
if (codeId != null) {
expectedEvent2.detail(Details.CODE_ID, codeId);
}
EventRepresentation eventRep2 = expectedEvent2.assertEvent();
Assert.assertEquals(eventRep1.getDetails().get(Details.CODE_ID), eventRep2.getDetails().get(Details.CODE_ID));
return eventRep2.getDetails().get(Details.CODE_ID);
}
public String updateProfile(String codeId) {

View file

@ -33,6 +33,8 @@ import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
@ -139,7 +141,8 @@ public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
// Second, change password
changePasswordPage.assertCurrent();
changePasswordPage.changePassword(NEW_PASSWORD, NEW_PASSWORD);
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
// Finally, update profile
updateProfilePage.assertCurrent();
@ -182,7 +185,8 @@ public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
// First, change password
changePasswordPage.assertCurrent();
changePasswordPage.changePassword(NEW_PASSWORD, NEW_PASSWORD);
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
// Second, update profile
updateProfilePage.assertCurrent();
@ -236,6 +240,7 @@ public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
changePasswordPage.assertCurrent();
changePasswordPage.changePassword(NEW_PASSWORD, NEW_PASSWORD);
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).assertEvent();
// Second, update profile
updateProfilePage.assertCurrent();
@ -299,7 +304,8 @@ public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
// First, change password
changePasswordPage.assertCurrent();
changePasswordPage.changePassword(NEW_PASSWORD, NEW_PASSWORD);
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
// Second, update profile
updateProfilePage.assertCurrent();
@ -395,7 +401,8 @@ public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
// change password
changePasswordPage.assertCurrent();
changePasswordPage.changePassword(NEW_PASSWORD, NEW_PASSWORD);
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
// CONFIGURE_TOTP
totpPage.assertCurrent();
@ -407,7 +414,8 @@ public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
TimeBasedOTP totp = new TimeBasedOTP();
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), "userLabel");
events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent();
events.expectRequiredAction(EventType.UPDATE_TOTP).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
// Logged in
appPage.assertCurrent();
@ -448,7 +456,7 @@ public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
appPage.assertCurrent();
assertThat(appPage.getRequestType(), is(RequestType.AUTH_RESPONSE));
events.expectLogin().assertEvent();
events.expect(EventType.UPDATE_CREDENTIAL).assertEvent();
}
private void enableRequiredActionForUser(final RequiredAction requiredAction) {

View file

@ -30,6 +30,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@ -106,7 +107,8 @@ public class RequiredActionResetPasswordTest extends AbstractTestRealmKeycloakTe
changePasswordPage.changePassword("new-password", "new-password");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -159,7 +161,8 @@ public class RequiredActionResetPasswordTest extends AbstractTestRealmKeycloakTe
.assertEvent();
}
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
EventRepresentation event2 = events.expectLogin().assertEvent();
List<UserSessionRepresentation> sessions = testUser.getUserSessions();

View file

@ -171,12 +171,23 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent()
String authSessionId1 = events.expectRequiredAction(EventType.UPDATE_TOTP)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setuptotp").assertEvent()
.getDetails().get(Details.CODE_ID);
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().user(userId).session(authSessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setuptotp").assertEvent()
.getDetails().get(Details.CODE_ID);
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().user(userId).session(authSessionId1).detail(Details.USERNAME, "setuptotp").assertEvent();
}
@Test
@ -380,17 +391,22 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
totpPage.configure(firstCode);
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
String authSessionId1 = events.expectRequiredAction(EventType.UPDATE_TOTP)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
String authSessionId2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
assertEquals(authSessionId1, authSessionId2);
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
EventRepresentation loginEvent = events.expectLogin().session(authSessionId).assertEvent();
EventRepresentation loginEvent = events.expectLogin().session(authSessionId1).assertEvent();
OAuthClient.AccessTokenResponse tokenResponse = sendTokenRequestAndGetResponse(loginEvent);
oauth.idTokenHint(tokenResponse.getIdToken()).openLogout();
events.expectLogout(authSessionId).assertEvent();
events.expectLogout(authSessionId1).assertEvent();
loginPage.open();
loginPage.login("test-user@localhost", "password");
@ -448,7 +464,14 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
// After totp config, user should be on the app page
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
events.expectRequiredAction(EventType.UPDATE_TOTP)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setuptotp2").assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setuptotp2").assertEvent();
EventRepresentation loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
@ -488,12 +511,21 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
totpPage.assertCurrent();
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent()
String sessionId1 = events.expectRequiredAction(EventType.UPDATE_TOTP)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setupTotp2").assertEvent()
.getDetails().get(Details.CODE_ID);
String sessionId2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "setupTotp2").assertEvent()
.getDetails().get(Details.CODE_ID);
assertEquals(sessionId1, sessionId2);
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setupTotp2").assertEvent();
events.expectLogin().user(userId).session(sessionId1).detail(Details.USERNAME, "setupTotp2").assertEvent();
}
@Test
@ -520,12 +552,17 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
TimeBasedOTP timeBased = new TimeBasedOTP(HmacOTP.HMAC_SHA1, 8, 30, 1);
totpPage.configure(timeBased.generateTOTP(totpSecret));
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
String sessionId1 = events.expectRequiredAction(EventType.UPDATE_TOTP)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
String sessionId2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
assertEquals(sessionId1, sessionId2);
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
EventRepresentation loginEvent = events.expectLogin().session(sessionId1).assertEvent();
OAuthClient.AccessTokenResponse tokenResponse = sendTokenRequestAndGetResponse(loginEvent);
oauth.idTokenHint(tokenResponse.getIdToken()).openLogout();
@ -575,12 +612,17 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
HmacOTP otpgen = new HmacOTP(6, HmacOTP.HMAC_SHA1, 1);
totpPage.configure(otpgen.generateHOTP(totpSecret, 0));
String uri = driver.getCurrentUrl();
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
.getDetails().get(Details.CODE_ID);
String sessionId1 = events.expectRequiredAction(EventType.UPDATE_TOTP)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
String sessionId2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent()
.getDetails().get(Details.CODE_ID);
assertEquals(sessionId1, sessionId2);
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
EventRepresentation loginEvent = events.expectLogin().session(sessionId1).assertEvent();
OAuthClient.AccessTokenResponse tokenResponse = sendTokenRequestAndGetResponse(loginEvent);
oauth.idTokenHint(tokenResponse.getIdToken()).openLogout();
@ -678,7 +720,14 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
.assertEvent();
}
EventRepresentation event2 = events.expectRequiredAction(EventType.UPDATE_TOTP).user(event1.getUserId()).detail(Details.USERNAME, "test-user@localhost").assertEvent();
events.expectRequiredAction(EventType.UPDATE_TOTP)
.user(event1.getUserId())
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "test-user@localhost").assertEvent();
EventRepresentation event2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(event1.getUserId())
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.detail(Details.USERNAME, "test-user@localhost").assertEvent();
event2 = events.expectLogin().user(event2.getUserId()).session(event2.getDetails().get(Details.CODE_ID)).detail(Details.USERNAME, "test-user@localhost").assertEvent();
// assert old session is gone or is maintained

View file

@ -43,6 +43,7 @@ import org.keycloak.federation.kerberos.KerberosConfig;
import org.keycloak.federation.kerberos.KerberosFederationProviderFactory;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.UserProfileAttributeMetadata;
@ -271,7 +272,9 @@ public class KerberosStandaloneTest extends AbstractKerberosSingleRealmTest {
driver.navigate().to(changePasswordUrl.trim());
loginPasswordUpdatePage.assertCurrent();
loginPasswordUpdatePage.changePassword("resetPassword", "resetPassword");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).client(oauth.getClientId()).detail(Details.USERNAME, "test-user@localhost");
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).client(oauth.getClientId()).detail(Details.USERNAME, "test-user@localhost");
events.poll();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).client(oauth.getClientId()).detail(Details.USERNAME, "test-user@localhost");
infoPage.assertCurrent();
Assert.assertEquals("Your account has been updated.", infoPage.getInfo());
}

View file

@ -27,6 +27,7 @@ import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.GroupModel;
import org.keycloak.models.LDAPConstants;
@ -570,7 +571,8 @@ public class LDAPProvidersIntegrationTest extends AbstractLDAPTest {
requiredActionChangePasswordPage.changePassword("Password1-updated2", "Password1-updated2");
appPage.assertCurrent();
events.expect(EventType.UPDATE_PASSWORD).user(userId).assertEvent();
events.expect(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).assertEvent();
events.expect(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).assertEvent();
loginEvent = events.expectLogin().user(userId).assertEvent();
tokenResponse = sendTokenRequestAndGetResponse(loginEvent);
appPage.logout(tokenResponse.getIdToken());

View file

@ -32,6 +32,7 @@ import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.OTPCredentialModel;
@ -175,7 +176,12 @@ public class UserStorageOTPTest extends AbstractTestRealmKeycloakTest {
appPage.assertCurrent();
// Logout
events.expect(EventType.UPDATE_TOTP).user(userRep.getId()).assertEvent(); //remove the UPDATE_TOTP event
events.expect(EventType.UPDATE_TOTP)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.user(userRep.getId()).assertEvent();
events.expect(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE)
.user(userRep.getId()).assertEvent();
EventRepresentation loginEvent = events.expectLogin().user(userRep.getId()).assertEvent();
String idTokenHint = sendTokenRequestAndGetResponse(loginEvent).getIdToken();
appPage.logout(idTokenHint);

View file

@ -30,6 +30,7 @@ import org.keycloak.events.EventType;
import org.keycloak.executors.ExecutorsProvider;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@ -784,7 +785,8 @@ public class BruteForceTest extends AbstractTestRealmKeycloakTest {
updatePasswordPage.updatePasswords("password", "password");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).assertEvent();
userRepresentation = testRealm().users().get(userId).toRepresentation();
assertTrue(userRepresentation.isEnabled());

View file

@ -754,7 +754,8 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest {
authenticateWithTotp();
totpSetupPage.assertCurrent();
totpSetupPage.configure(totp.generateTOTP(totpSetupPage.getTotpSecret()), "totp2-label");
events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent();
events.expectRequiredAction(EventType.UPDATE_TOTP).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
TokenCtx token2 = assertLoggedInWithAcr("gold");
// Trying to add another OTP by "kc_action". Level 2 should be required and user can choose between 2 OTP codes
@ -803,7 +804,8 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest {
authenticateWithTotp();
totpSetupPage.assertCurrent();
totpSetupPage.configure(totp.generateTOTP(totpSetupPage.getTotpSecret()), "totp2-label");
events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent();
events.expectRequiredAction(EventType.UPDATE_TOTP).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
TokenCtx token2 = assertLoggedInWithAcr("gold");
String otp2CredentialId = getCredentialIdByLabel("totp2-label");
@ -818,7 +820,8 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest {
deleteCredentialPage.assertCredentialInMessage("totp2-label");
deleteCredentialPage.confirm();
events.expectRequiredAction(EventType.REMOVE_TOTP).assertEvent();
events.expectRequiredAction(EventType.REMOVE_TOTP).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.REMOVE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
assertLoggedInWithAcr("gold");
}
@ -841,7 +844,8 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest {
deleteCredentialPage.assertCurrent();
deleteCredentialPage.assertCredentialInMessage("otp");
deleteCredentialPage.confirm();
events.expectRequiredAction(EventType.REMOVE_TOTP).assertEvent();
events.expectRequiredAction(EventType.REMOVE_TOTP).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.REMOVE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
assertLoggedInWithAcr("gold");
// Trying to add OTP. No 2nd factor should be required as user doesn't have any
@ -850,7 +854,8 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest {
totpSetupPage.assertCurrent();
String totp2Secret = totpSetupPage.getTotpSecret();
totpSetupPage.configure(totp.generateTOTP(totp2Secret), "totp2-label");
events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent();
events.expectRequiredAction(EventType.UPDATE_TOTP).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, OTPCredentialModel.TYPE).assertEvent();
assertLoggedInWithAcr("silver");
// set time offset for OTP as it is not permitted to authenticate with same OTP code multiple times
@ -881,7 +886,7 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest {
deleteCredentialPage.assertCurrent();
deleteCredentialPage.assertCredentialInMessage("Recovery codes");
deleteCredentialPage.confirm();
events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION).assertEvent();
events.expectRequiredAction(EventType.REMOVE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, RecoveryAuthnCodesCredentialModel.TYPE).assertEvent();
assertLoggedInWithAcr("gold");
} finally {
setOtpTimeOffset(0, totp);

View file

@ -44,6 +44,7 @@ import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.utils.SessionTimeoutHelper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@ -210,7 +211,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
//POST request to http://localhost:8180/auth/realms/test/protocol/openid-connect/auth;
UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(AUTH_SERVER_ROOT));
Response response = client.target(b.build(oauth.getRealm())).request().post(oauth.getLoginEntityForPOST());
assertThat(response.getStatus(), is(equalTo(200)));
assertThat(response, Matchers.body(containsString("Sign in")));
@ -531,7 +532,8 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
setTimeOffset(0);
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).detail(Details.USERNAME, "login-test").assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).detail(Details.USERNAME, "login-test").assertEvent();
String currentUrl = driver.getCurrentUrl();
String pageSource = driver.getPageSource();
@ -710,14 +712,14 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
//login without remember me
loginPage.setRememberMe(false);
loginPage.login("login-test", "password");
// Expire session
loginEvent = events.expectLogin().user(userId)
.detail(Details.USERNAME, "login-test")
.assertEvent();
sessionId = loginEvent.getSessionId();
testingClient.testing().removeUserSession("test", sessionId);
// Assert rememberMe not checked nor username/email prefilled
loginPage.open();
assertFalse(loginPage.isRememberMeChecked());
@ -726,7 +728,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
setRememberMe(false);
}
}
@Test
// KEYCLOAK-3181
public void loginWithEmailUserAndRememberMe() {
@ -752,7 +754,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
// Assert rememberMe checked and username/email prefilled
loginPage.open();
assertTrue(loginPage.isRememberMeChecked());
Assert.assertEquals("login@test.com", loginPage.getUsername());
loginPage.setRememberMe(false);

View file

@ -30,6 +30,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.utils.SystemClientUtil;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
@ -239,6 +240,18 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
updatePasswordPage.changePassword("resetPassword", "resetPassword");
event = events.expectRequiredAction(EventType.UPDATE_PASSWORD)
.detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE)
.client(expectedClientId)
.user(userId).detail(Details.USERNAME, username);
if (expectedRedirectUri != null) {
event.detail(Details.REDIRECT_URI, expectedRedirectUri);
} else {
event.removeDetail(Details.REDIRECT_URI);
}
event.assertEvent();
event = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE)
.client(expectedClientId)
.user(userId).detail(Details.USERNAME, username);
if (expectedRedirectUri != null) {
@ -414,7 +427,8 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
updatePasswordPage.changePassword(password, password);
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, username.trim()).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).detail(Details.USERNAME, username.trim()).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).detail(Details.USERNAME, username.trim()).assertEvent();
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -465,7 +479,8 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
updatePasswordPage.assertCurrent();
assertEquals(error, updatePasswordPage.getError());
events.expectRequiredAction(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL_ERROR).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).error(Errors.PASSWORD_REJECTED).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_PASSWORD_ERROR).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).error(Errors.PASSWORD_REJECTED).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
}
private void initiateResetPasswordFromResetPasswordPage(String username) {
@ -1040,11 +1055,13 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
assertEquals("Invalid password: minimum length 8.", resetPasswordPage.getErrorMessage());
events.expectRequiredAction(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL_ERROR).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).error(Errors.PASSWORD_REJECTED).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_PASSWORD_ERROR).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).error(Errors.PASSWORD_REJECTED).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -1397,6 +1414,12 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
changePasswordOnUpdatePage(driver);
events.expectRequiredAction(EventType.UPDATE_PASSWORD)
.detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE)
.detail(Details.REDIRECT_URI, redirectUri)
.client(clientId)
.user(user.getId()).detail(Details.USERNAME, user.getUsername()).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE)
.detail(Details.REDIRECT_URI, redirectUri)
.client(clientId)
.user(user.getId()).detail(Details.USERNAME, user.getUsername()).assertEvent();

View file

@ -26,6 +26,7 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@ -191,7 +192,8 @@ public class SSOTest extends AbstractTestRealmKeycloakTest {
updatePasswordPage.assertCurrent();
updatePasswordPage.changePassword("password", "password");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL).detail(Details.CREDENTIAL_TYPE, PasswordCredentialModel.TYPE).assertEvent();
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());

View file

@ -218,13 +218,22 @@ public class WebAuthnIdlessTest extends AbstractWebAuthnVirtualTest {
appPage.assertCurrent();
assertThat(appPage.getRequestType(), is(RequestType.AUTH_RESPONSE));
EventRepresentation eventRep = events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION)
EventRepresentation eventRep1 = events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION)
.user(userId)
.detail(Details.CUSTOM_REQUIRED_ACTION, raProviderID)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, authenticatorLabel)
.detail(WebAuthnConstants.PUBKEY_CRED_AAGUID_ATTR, ALL_ZERO_AAGUID)
.assertEvent();
String credentialId = eventRep.getDetails().get(WebAuthnConstants.PUBKEY_CRED_ID_ATTR);
EventRepresentation eventRep2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CUSTOM_REQUIRED_ACTION, raProviderID)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, authenticatorLabel)
.detail(WebAuthnConstants.PUBKEY_CRED_AAGUID_ATTR, ALL_ZERO_AAGUID)
.assertEvent();
String credentialId1 = eventRep1.getDetails().get(WebAuthnConstants.PUBKEY_CRED_ID_ATTR);
String credentialId2 = eventRep2.getDetails().get(WebAuthnConstants.PUBKEY_CRED_ID_ATTR);
assertThat(credentialId1, equalTo(credentialId2));
assertThat(userRes.credentials().stream()
.filter(cred -> cred.getType().equals(credType))
@ -250,7 +259,7 @@ public class WebAuthnIdlessTest extends AbstractWebAuthnVirtualTest {
.user(userId)
.client("account")
.assertEvent();
return credentialId;
return credentialId2;
}
protected void checkTryAnotherWay() {

View file

@ -31,6 +31,7 @@ import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterF
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.credential.WebAuthnCredentialModel;
import org.keycloak.models.credential.dto.WebAuthnCredentialData;
import org.keycloak.representations.idm.CredentialRepresentation;
@ -58,11 +59,11 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.events.EventType.CUSTOM_REQUIRED_ACTION;
import static org.keycloak.models.AuthenticationExecutionModel.Requirement.ALTERNATIVE;
import static org.keycloak.models.AuthenticationExecutionModel.Requirement.REQUIRED;
@ -127,13 +128,22 @@ public class WebAuthnRegisterAndLoginTest extends AbstractWebAuthnVirtualTest {
// confirm that registration is successfully completed
userId = events.expectRegister(username, email).assertEvent().getUserId();
// confirm registration event
EventRepresentation eventRep = events.expectRequiredAction(CUSTOM_REQUIRED_ACTION)
EventRepresentation eventRep1 = events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION)
.user(userId)
.detail(Details.CUSTOM_REQUIRED_ACTION, WebAuthnRegisterFactory.PROVIDER_ID)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, authenticatorLabel)
.detail(WebAuthnConstants.PUBKEY_CRED_AAGUID_ATTR, ALL_ZERO_AAGUID)
.assertEvent();
String regPubKeyCredentialId = eventRep.getDetails().get(WebAuthnConstants.PUBKEY_CRED_ID_ATTR);
EventRepresentation eventRep2 = events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CUSTOM_REQUIRED_ACTION, WebAuthnRegisterFactory.PROVIDER_ID)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, authenticatorLabel)
.detail(WebAuthnConstants.PUBKEY_CRED_AAGUID_ATTR, ALL_ZERO_AAGUID)
.assertEvent();
String regPubKeyCredentialId1 = eventRep1.getDetails().get(WebAuthnConstants.PUBKEY_CRED_ID_ATTR);
String regPubKeyCredentialId2 = eventRep2.getDetails().get(WebAuthnConstants.PUBKEY_CRED_ID_ATTR);
assertThat(regPubKeyCredentialId1, equalTo(regPubKeyCredentialId2));
// confirm login event
String sessionId = events.expectLogin()
@ -176,7 +186,7 @@ public class WebAuthnRegisterAndLoginTest extends AbstractWebAuthnVirtualTest {
// confirm login event
sessionId = events.expectLogin()
.user(userId)
.detail(WebAuthnConstants.PUBKEY_CRED_ID_ATTR, regPubKeyCredentialId)
.detail(WebAuthnConstants.PUBKEY_CRED_ID_ATTR, regPubKeyCredentialId2)
.detail(WebAuthnConstants.USER_VERIFICATION_CHECKED, Boolean.FALSE.toString())
.assertEvent().getSessionId();
@ -235,7 +245,12 @@ public class WebAuthnRegisterAndLoginTest extends AbstractWebAuthnVirtualTest {
webAuthnRegisterPage.assertCurrent();
events.expectRequiredAction(CUSTOM_REQUIRED_ACTION)
events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION)
.user(userId)
.detail(Details.CUSTOM_REQUIRED_ACTION, WebAuthnRegisterFactory.PROVIDER_ID)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, WEBAUTHN_LABEL)
.assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CUSTOM_REQUIRED_ACTION, WebAuthnRegisterFactory.PROVIDER_ID)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, WEBAUTHN_LABEL)
@ -246,7 +261,12 @@ public class WebAuthnRegisterAndLoginTest extends AbstractWebAuthnVirtualTest {
appPage.assertCurrent();
events.expectRequiredAction(CUSTOM_REQUIRED_ACTION)
events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION)
.user(userId)
.detail(Details.CUSTOM_REQUIRED_ACTION, WebAuthnPasswordlessRegisterFactory.PROVIDER_ID)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, PASSWORDLESS_LABEL)
.assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CUSTOM_REQUIRED_ACTION, WebAuthnPasswordlessRegisterFactory.PROVIDER_ID)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, PASSWORDLESS_LABEL)

View file

@ -86,6 +86,14 @@ public class WebAuthnOtherSettingsTest extends AbstractWebAuthnVirtualTest {
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, "webauthn")
.detail(WebAuthnConstants.PUBKEY_CRED_AAGUID_ATTR, ALL_ZERO_AAGUID)
.assertEvent();
events.expectRequiredAction(EventType.UPDATE_CREDENTIAL)
.user(userId)
.detail(Details.CUSTOM_REQUIRED_ACTION, isPasswordless()
? WebAuthnPasswordlessRegisterFactory.PROVIDER_ID
: WebAuthnRegisterFactory.PROVIDER_ID)
.detail(WebAuthnConstants.PUBKEY_CRED_LABEL_ATTR, "webauthn")
.detail(WebAuthnConstants.PUBKEY_CRED_AAGUID_ATTR, ALL_ZERO_AAGUID)
.assertEvent();
final String credentialType = getCredentialType();
// Soft token in Firefox does not increment counter

View file

@ -13,7 +13,7 @@
"failureFactor" : 3,
"eventsEnabled" : true,
"eventsListeners" : [ "jboss-logging" ],
"enabledEventTypes" : [ "SEND_RESET_PASSWORD", "UPDATE_TOTP", "REMOVE_TOTP", "REVOKE_GRANT", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "RESTART_AUTHENTICATION", "UPDATE_PROFILE_ERROR", "IMPERSONATE", "LOGIN", "UPDATE_PASSWORD_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "REGISTER", "LOGOUT", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "UPDATE_PASSWORD", "FEDERATED_IDENTITY_LINK_ERROR", "CLIENT_DELETE", "IDENTITY_PROVIDER_FIRST_LOGIN", "VERIFY_EMAIL", "CLIENT_DELETE_ERROR", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "REMOVE_FEDERATED_IDENTITY_ERROR", "EXECUTE_ACTIONS", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "SEND_VERIFY_EMAIL", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "EXECUTE_ACTION_TOKEN", "CLIENT_UPDATE_ERROR", "UPDATE_PROFILE", "FEDERATED_IDENTITY_LINK", "CLIENT_REGISTER_ERROR", "SEND_VERIFY_EMAIL_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "CUSTOM_REQUIRED_ACTION_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ],
"enabledEventTypes" : [ "SEND_RESET_PASSWORD", "REMOVE_CREDENTIAL", "UPDATE_TOTP", "REMOVE_TOTP", "REVOKE_GRANT", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "RESTART_AUTHENTICATION", "UPDATE_PROFILE_ERROR", "IMPERSONATE", "LOGIN", "UPDATE_CREDENTIAL_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "REGISTER", "LOGOUT", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "UPDATE_CREDENTIAL", "UPDATE_PASSWORD", "FEDERATED_IDENTITY_LINK_ERROR", "CLIENT_DELETE", "IDENTITY_PROVIDER_FIRST_LOGIN", "VERIFY_EMAIL", "CLIENT_DELETE_ERROR", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "REMOVE_FEDERATED_IDENTITY_ERROR", "EXECUTE_ACTIONS", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "SEND_VERIFY_EMAIL", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "EXECUTE_ACTION_TOKEN", "CLIENT_UPDATE_ERROR", "UPDATE_PROFILE", "FEDERATED_IDENTITY_LINK", "CLIENT_REGISTER_ERROR", "SEND_VERIFY_EMAIL_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "REMOVE_CREDENTIAL_ERROR", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "CUSTOM_REQUIRED_ACTION_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ],
"adminEventsEnabled" : true,
"adminEventsDetailsEnabled" : true,
"requiredCredentials":[

View file

@ -14,7 +14,7 @@
"failureFactor" : 3,
"eventsEnabled" : true,
"eventsListeners" : [ "jboss-logging" ],
"enabledEventTypes" : [ "SEND_RESET_PASSWORD", "UPDATE_TOTP", "REMOVE_TOTP", "REVOKE_GRANT", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "RESTART_AUTHENTICATION", "UPDATE_PROFILE_ERROR", "IMPERSONATE", "LOGIN", "UPDATE_PASSWORD_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "REGISTER", "LOGOUT", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "UPDATE_PASSWORD", "FEDERATED_IDENTITY_LINK_ERROR", "CLIENT_DELETE", "IDENTITY_PROVIDER_FIRST_LOGIN", "VERIFY_EMAIL", "CLIENT_DELETE_ERROR", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "REMOVE_FEDERATED_IDENTITY_ERROR", "EXECUTE_ACTIONS", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "SEND_VERIFY_EMAIL", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "EXECUTE_ACTION_TOKEN", "CLIENT_UPDATE_ERROR", "UPDATE_PROFILE", "FEDERATED_IDENTITY_LINK", "CLIENT_REGISTER_ERROR", "SEND_VERIFY_EMAIL_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "CUSTOM_REQUIRED_ACTION_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ],
"enabledEventTypes" : [ "SEND_RESET_PASSWORD", "UPDATE_TOTP", "REMOVE_TOTP", "REMOVE_CREDENTIAL", "REVOKE_GRANT", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "RESTART_AUTHENTICATION", "UPDATE_PROFILE_ERROR", "IMPERSONATE", "LOGIN", "UPDATE_PASSWORD_ERROR", "UPDATE_CREDENTIAL_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "REGISTER", "LOGOUT", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "UPDATE_CREDENTIAL", "UPDATE_PASSWORD", "FEDERATED_IDENTITY_LINK_ERROR", "CLIENT_DELETE", "IDENTITY_PROVIDER_FIRST_LOGIN", "VERIFY_EMAIL", "CLIENT_DELETE_ERROR", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "REMOVE_FEDERATED_IDENTITY_ERROR", "EXECUTE_ACTIONS", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "SEND_VERIFY_EMAIL", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "EXECUTE_ACTION_TOKEN", "CLIENT_UPDATE_ERROR", "UPDATE_PROFILE", "FEDERATED_IDENTITY_LINK", "CLIENT_REGISTER_ERROR", "SEND_VERIFY_EMAIL_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "REMOVE_CREDENTIAL_ERROR", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "CUSTOM_REQUIRED_ACTION_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ],
"adminEventsEnabled" : true,
"adminEventsDetailsEnabled" : true,
"requiredCredentials":[

View file

@ -0,0 +1,4 @@
<#import "template.ftl" as layout>
<@layout.emailLayout>
${kcSanitize(msg("eventRemoveCredentialBodyHtml", event.details.credential_type!"unknown", event.date, event.ipAddress))?no_esc}
</@layout.emailLayout>

View file

@ -0,0 +1,4 @@
<#import "template.ftl" as layout>
<@layout.emailLayout>
${kcSanitize(msg("eventUpdateCredentialBodyHtml", event.details.credential_type!"unknown", event.date, event.ipAddress))?no_esc}
</@layout.emailLayout>

View file

@ -33,6 +33,12 @@ eventUpdatePasswordBodyHtml=<p>Your password was changed on {0} from {1}. If thi
eventUpdateTotpSubject=Update OTP
eventUpdateTotpBody=OTP was updated for your account on {0} from {1}. If this was not you, please contact an administrator.
eventUpdateTotpBodyHtml=<p>OTP was updated for your account on {0} from {1}. If this was not you, please contact an administrator.</p>
eventUpdateCredentialSubject=Update credential
eventUpdateCredentialBody=Your {0} credential was changed on {1} from {2}. If this was not you, please contact an administrator.
eventUpdateCredentialBodyHtml=<p>Your {0} credential was changed on {1} from {2}. If this was not you, please contact an administrator.</p>
eventRemoveCredentialSubject=Remove credential
eventRemoveCredentialBody=Credential {0} was removed from your account on {1} from {2}. If this was not you, please contact an administrator.
eventRemoveCredentialBodyHtml=<p>Credential {0} was removed from your account on {1} from {2}. If this was not you, please contact an administrator.</p>
requiredAction.CONFIGURE_TOTP=Configure OTP
requiredAction.TERMS_AND_CONDITIONS=Terms and Conditions

View file

@ -0,0 +1,2 @@
<#ftl output_format="plainText">
${msg("eventRemoveCredentialBody", event.details.credential_type!"unknown", event.date, event.ipAddress)}

View file

@ -0,0 +1,2 @@
<#ftl output_format="plainText">
${msg("eventUpdateCredentialBody", event.details.credential_type!"unknown", event.date, event.ipAddress)}