diff --git a/src/main/java/io/phasetwo/service/auth/idp/HomeIdpDiscoveryAuthenticator.java b/src/main/java/io/phasetwo/service/auth/idp/HomeIdpDiscoveryAuthenticator.java index 695e3d26..c932fc27 100755 --- a/src/main/java/io/phasetwo/service/auth/idp/HomeIdpDiscoveryAuthenticator.java +++ b/src/main/java/io/phasetwo/service/auth/idp/HomeIdpDiscoveryAuthenticator.java @@ -13,12 +13,15 @@ import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.services.managers.AuthenticationManager; import java.util.List; +import static org.keycloak.protocol.oidc.OIDCLoginProtocol.LOGIN_HINT_PARAM; import static org.keycloak.services.validation.Validation.FIELD_USERNAME; final class HomeIdpDiscoveryAuthenticator extends AbstractUsernameFormAuthenticator { @@ -106,8 +109,12 @@ public void action(AuthenticationFlowContext authenticationFlowContext) { final List homeIdps = context.discoverer(discovererConfig).discoverForUser(authenticationFlowContext, username); if (homeIdps.isEmpty()) { - authenticationFlowContext.attempted(); - context.loginHint().setInAuthSession(username); + if (authenticationFlowContext.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { + authenticationFlowContext.success(); + } else { + authenticationFlowContext.attempted(); + context.loginHint().setInAuthSession(username); + } } else { RememberMe rememberMe = context.rememberMe(); rememberMe.handleAction(formData); @@ -117,6 +124,7 @@ public void action(AuthenticationFlowContext authenticationFlowContext) { } private String setUserInContext(AuthenticationFlowContext context, String username) { + context.clearUser(); username = trimToNull(username); if (username == null) { @@ -130,6 +138,19 @@ private String setUserInContext(AuthenticationFlowContext context, String userna LOG.debugf("Found username '%s' in request", username); context.getEvent().detail(Details.USERNAME, username); context.getAuthenticationSession().setAuthNote(ATTEMPTED_USERNAME, username); + context.getAuthenticationSession().setClientNote(LOGIN_HINT_PARAM, username); + + try { + UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), + username); + if (user != null) { + LOG.tracef("Setting user '%s' in context", user.getId()); + context.setUser(user); + } + } catch (ModelDuplicateException ex) { + LOG.warnf(ex, "Could not uniquely identify the user. Multiple users with name or email '%s' found.", + username); + } return username; } diff --git a/src/main/java/io/phasetwo/service/auth/idp/discovery/email/EmailHomeIdpDiscoverer.java b/src/main/java/io/phasetwo/service/auth/idp/discovery/email/EmailHomeIdpDiscoverer.java index 7456e5ca..dbbe689d 100644 --- a/src/main/java/io/phasetwo/service/auth/idp/discovery/email/EmailHomeIdpDiscoverer.java +++ b/src/main/java/io/phasetwo/service/auth/idp/discovery/email/EmailHomeIdpDiscoverer.java @@ -97,6 +97,8 @@ private List discoverHomeIdps(AuthenticationFlowContext c Collectors.toMap(FederatedIdentityModel::getIdentityProvider, FederatedIdentityModel::getUserName)); } + + //Todo: This logic should be moved in a custom identity provider class. see OrgsIdentityProviders List candidateIdps = identityProviders.candidatesForHomeIdp(context, user); if (candidateIdps == null) { candidateIdps = emptyList(); @@ -112,7 +114,7 @@ private List discoverHomeIdps(AuthenticationFlowContext c OrganizationProvider orgs = context.getSession().getProvider(OrganizationProvider.class); List idpsWithMatchingDomain = orgs.getOrganizationsStreamForDomain( - context.getRealm(), domain.toString(), config.forwardUserWithUnverifiedEmail()) + context.getRealm(), domain.toString(), config.requireVerifiedDomain()) .flatMap(OrganizationModel::getIdentityProvidersStream) .filter(IdentityProviderModel::isEnabled) .collect(Collectors.toList()); diff --git a/src/main/java/io/phasetwo/service/auth/idp/discovery/email/EmailHomeIdpDiscovererConfig.java b/src/main/java/io/phasetwo/service/auth/idp/discovery/email/EmailHomeIdpDiscovererConfig.java index 0a44f1be..6cedd4cf 100644 --- a/src/main/java/io/phasetwo/service/auth/idp/discovery/email/EmailHomeIdpDiscovererConfig.java +++ b/src/main/java/io/phasetwo/service/auth/idp/discovery/email/EmailHomeIdpDiscovererConfig.java @@ -16,6 +16,7 @@ final class EmailHomeIdpDiscovererConfig { private static final String FORWARD_TO_LINKED_IDP = "forwardToLinkedIdp"; private static final String USER_ATTRIBUTE = "userAttribute"; private static final String FORWARD_UNVERIFIED_ATTRIBUTE = "forwardUnverifiedEmail"; + static final String REQUIRE_VERIFIED_DOMAIN = "requireVerifiedDomain"; private static final ProviderConfigProperty FORWARD_TO_LINKED_IDP_PROPERTY = new ProviderConfigProperty( FORWARD_TO_LINKED_IDP, @@ -41,10 +42,19 @@ final class EmailHomeIdpDiscovererConfig { false, false); + private static final ProviderConfigProperty REQUIRE_VERIFIED_DOMAIN_PROPERTY = new ProviderConfigProperty( + REQUIRE_VERIFIED_DOMAIN, + "Require a verified domain", + "Whether a verified domain name for an organization is required to forward to their identity provider.", + BOOLEAN_TYPE, + false, + false); + static final List CONFIG_PROPERTIES = ProviderConfigurationBuilder.create() .property(USER_ATTRIBUTE_PROPERTY) .property(FORWARD_UNVERIFIED_PROPERTY) .property(FORWARD_TO_LINKED_IDP_PROPERTY) + .property(REQUIRE_VERIFIED_DOMAIN_PROPERTY) .build(); private final AuthenticatorConfigModel authenticatorConfigModel; @@ -70,6 +80,12 @@ boolean forwardUserWithUnverifiedEmail() { .orElse(false); } + boolean requireVerifiedDomain() { + return Optional.ofNullable(authenticatorConfigModel) + .map(it -> Boolean.parseBoolean(it.getConfig().getOrDefault(REQUIRE_VERIFIED_DOMAIN, "false"))) + .orElse(false); + } + String getAlias() { return Optional.ofNullable(authenticatorConfigModel) .map(AuthenticatorConfigModel::getAlias)