trigger IDENTITY_PROVIDER_FIRST_LOGIN (and UPDATE_PROFILE ) event when identity provider flow succeeds (#15100)

closes #15098
This commit is contained in:
Zakaria Amine 2023-03-03 17:49:27 +01:00 committed by GitHub
parent 6d2e57f93a
commit fb5a7f654b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 5 deletions

View file

@ -107,7 +107,6 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
@Override @Override
protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) { protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
EventBuilder event = context.getEvent(); EventBuilder event = context.getEvent();
//velias: looks like UPDATE_PROFILE event is not fired. IMHO it should not be fired here as user record in keycloak is not changed, user doesn't exist yet
event.event(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name()); event.event(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name());
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters(); MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
UserModelDelegate updatedProfile = new UserModelDelegate(null) { UserModelDelegate updatedProfile = new UserModelDelegate(null) {

View file

@ -773,6 +773,7 @@ public class LoginActionsService {
SessionCodeChecks checks = checksForCode(authSessionId, code, execution, clientId, tabId, flowPath); SessionCodeChecks checks = checksForCode(authSessionId, code, execution, clientId, tabId, flowPath);
if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) { if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
event.error("Failed to verify login action");
return checks.getResponse(); return checks.getResponse();
} }
event.detail(Details.CODE_ID, code); event.detail(Details.CODE_ID, code);
@ -784,7 +785,9 @@ public class LoginActionsService {
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, noteKey); SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, noteKey);
if (serializedCtx == null) { if (serializedCtx == null) {
ServicesLogger.LOGGER.notFoundSerializedCtxInClientSession(noteKey); ServicesLogger.LOGGER.notFoundSerializedCtxInClientSession(noteKey);
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, "Not found serialized context in authenticationSession.")); String message = "Not found serialized context in authenticationSession.";
event.error(message);
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, message));
} }
BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, authSession); BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, authSession);
final String identityProviderAlias = brokerContext.getIdpConfig().getAlias(); final String identityProviderAlias = brokerContext.getIdpConfig().getAlias();
@ -792,17 +795,23 @@ public class LoginActionsService {
String flowId = firstBrokerLogin ? brokerContext.getIdpConfig().getFirstBrokerLoginFlowId() : brokerContext.getIdpConfig().getPostBrokerLoginFlowId(); String flowId = firstBrokerLogin ? brokerContext.getIdpConfig().getFirstBrokerLoginFlowId() : brokerContext.getIdpConfig().getPostBrokerLoginFlowId();
if (flowId == null) { if (flowId == null) {
ServicesLogger.LOGGER.flowNotConfigForIDP(identityProviderAlias); ServicesLogger.LOGGER.flowNotConfigForIDP(identityProviderAlias);
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, "Flow not configured for identity provider")); String message = "Flow not configured for identity provider";
event.error(message);
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, message));
} }
AuthenticationFlowModel brokerLoginFlow = realm.getAuthenticationFlowById(flowId); AuthenticationFlowModel brokerLoginFlow = realm.getAuthenticationFlowById(flowId);
if (brokerLoginFlow == null) { if (brokerLoginFlow == null) {
ServicesLogger.LOGGER.flowNotFoundForIDP(flowId, identityProviderAlias); ServicesLogger.LOGGER.flowNotFoundForIDP(flowId, identityProviderAlias);
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, "Flow not found for identity provider")); String message = "Flow not found for identity provider";
event.error(message);
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, message));
} }
event.detail(Details.IDENTITY_PROVIDER, identityProviderAlias) event.detail(Details.IDENTITY_PROVIDER, identityProviderAlias)
.detail(Details.IDENTITY_PROVIDER_USERNAME, brokerContext.getUsername()); .detail(Details.IDENTITY_PROVIDER_USERNAME, brokerContext.getUsername());
event.success();
AuthenticationProcessor processor = new AuthenticationProcessor() { AuthenticationProcessor processor = new AuthenticationProcessor() {
@Override @Override

View file

@ -45,6 +45,7 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.assertHardCodedSessionNote; import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.assertHardCodedSessionNote;
import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.configureAutoLinkFlow; import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.configureAutoLinkFlow;
import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS;
import static org.keycloak.testsuite.broker.BrokerTestConstants.USER_EMAIL; import static org.keycloak.testsuite.broker.BrokerTestConstants.USER_EMAIL;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot; import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot; import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot;
@ -1007,6 +1008,16 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
Assert.assertEquals("no-first-name", accountUpdateProfilePage.getUsername()); Assert.assertEquals("no-first-name", accountUpdateProfilePage.getUsername());
RealmRepresentation consumerRealmRep = adminClient.realm(bc.consumerRealmName()).toRepresentation(); RealmRepresentation consumerRealmRep = adminClient.realm(bc.consumerRealmName()).toRepresentation();
events.expectAccount(EventType.IDENTITY_PROVIDER_FIRST_LOGIN).realm(consumerRealmRep).user((String)null)
.detail(Details.IDENTITY_PROVIDER, bc.getIDPAlias())
.detail(Details.IDENTITY_PROVIDER_USERNAME, "no-first-name")
.assertEvent(getFirstConsumerEvent());
events.expectAccount(EventType.UPDATE_PROFILE).realm(consumerRealmRep).user((String)null)
.detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name())
.assertEvent(getFirstConsumerEvent());
events.expectAccount(EventType.REGISTER).realm(consumerRealmRep).user(Matchers.any(String.class)).session((String) null) events.expectAccount(EventType.REGISTER).realm(consumerRealmRep).user(Matchers.any(String.class)).session((String) null)
.detail(Details.IDENTITY_PROVIDER_USERNAME, "no-first-name") .detail(Details.IDENTITY_PROVIDER_USERNAME, "no-first-name")
.detail(Details.REGISTER_METHOD, "broker") .detail(Details.REGISTER_METHOD, "broker")
@ -1044,6 +1055,16 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
Assert.assertEquals("no-first-name", accountUpdateProfilePage.getUsername()); Assert.assertEquals("no-first-name", accountUpdateProfilePage.getUsername());
RealmRepresentation consumerRealmRep = adminClient.realm(bc.consumerRealmName()).toRepresentation(); RealmRepresentation consumerRealmRep = adminClient.realm(bc.consumerRealmName()).toRepresentation();
events.expectAccount(EventType.IDENTITY_PROVIDER_FIRST_LOGIN).realm(consumerRealmRep).user((String)null)
.detail(Details.IDENTITY_PROVIDER, bc.getIDPAlias())
.detail(Details.IDENTITY_PROVIDER_USERNAME, "no-first-name")
.assertEvent(getFirstConsumerEvent());
events.expectAccount(EventType.UPDATE_PROFILE).realm(consumerRealmRep).user((String)null)
.detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name())
.assertEvent(getFirstConsumerEvent());
events.expectAccount(EventType.UPDATE_EMAIL).realm(consumerRealmRep).user((String)null).session((String) null) events.expectAccount(EventType.UPDATE_EMAIL).realm(consumerRealmRep).user((String)null).session((String) null)
.detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name()) .detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name())
.detail(Details.IDENTITY_PROVIDER_USERNAME, "no-first-name") .detail(Details.IDENTITY_PROVIDER_USERNAME, "no-first-name")

View file

@ -27,6 +27,10 @@ import org.keycloak.events.Details;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.userprofile.UserProfileContext;
import static org.keycloak.testsuite.AssertEvents.DEFAULT_USERNAME;
import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS;
/** /**
* Simple test to check the events after a broker login using OIDC. It also * Simple test to check the events after a broker login using OIDC. It also
@ -68,6 +72,21 @@ public final class KcOidcBrokerEventTest extends AbstractBrokerTest {
.client(bc.getIDPClientIdInProviderRealm()) .client(bc.getIDPClientIdInProviderRealm())
.assertEvent(); .assertEvent();
events.expect(EventType.IDENTITY_PROVIDER_FIRST_LOGIN)
.realm(consumerRealm.toRepresentation().getId())
.client("account")
.user((String)null)
.detail(Details.IDENTITY_PROVIDER, IDP_OIDC_ALIAS)
.detail(Details.IDENTITY_PROVIDER_USERNAME, bc.getUserLogin())
.assertEvent();
events.expect(EventType.UPDATE_PROFILE)
.realm(consumerRealm.toRepresentation().getId())
.client("account")
.user((String)null)
.detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name())
.assertEvent();
events.expect(EventType.REGISTER) events.expect(EventType.REGISTER)
.realm(consumerRealm.toRepresentation().getId()) .realm(consumerRealm.toRepresentation().getId())
.client("account") .client("account")
@ -87,7 +106,7 @@ public final class KcOidcBrokerEventTest extends AbstractBrokerTest {
.detail(Details.IDENTITY_PROVIDER_USERNAME, bc.getUserLogin()) .detail(Details.IDENTITY_PROVIDER_USERNAME, bc.getUserLogin())
.detail(Details.IDENTITY_PROVIDER, bc.getIDPAlias()) .detail(Details.IDENTITY_PROVIDER, bc.getIDPAlias())
.assertEvent(); .assertEvent();
events.clear(); events.clear();
} }