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

@ -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.

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

@ -3242,3 +3242,11 @@ 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?
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;
@ -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

@ -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;
@ -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();

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)}