keycloak-memoizeit
Changes
services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpAutoLinkAuthenticator.java 67(+67 -0)
services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpAutoLinkAuthenticatorFactory.java 100(+100 -0)
services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory 1(+1 -0)
Details
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpAutoLinkAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpAutoLinkAuthenticator.java
new file mode 100644
index 0000000..cf31bbe
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpAutoLinkAuthenticator.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.authentication.authenticators.broker;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import static org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator.getExistingUser;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.sessions.AuthenticationSessionModel;
+
+/**
+ * @author <a href="mailto:Ryan.Slominski@gmail.com">Ryan Slominski</a>
+ */
+public class IdpAutoLinkAuthenticator extends AbstractIdpAuthenticator {
+
+ private static Logger logger = Logger.getLogger(IdpAutoLinkAuthenticator.class);
+
+ @Override
+ protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+ KeycloakSession session = context.getSession();
+ RealmModel realm = context.getRealm();
+ AuthenticationSessionModel authSession = context.getAuthenticationSession();
+
+ UserModel existingUser = getExistingUser(session, realm, authSession);
+
+ logger.debugf("User '%s' will auto link with identity provider '%s' . Identity provider username is '%s' ", existingUser.getUsername(),
+ brokerContext.getIdpConfig().getAlias(), brokerContext.getUsername());
+
+ context.setUser(existingUser);
+ context.success();
+ }
+
+ @Override
+ protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+ authenticateImpl(context, serializedCtx, brokerContext);
+ }
+
+ @Override
+ public boolean requiresUser() {
+ return false;
+ }
+
+ @Override
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return false;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpAutoLinkAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpAutoLinkAuthenticatorFactory.java
new file mode 100644
index 0000000..1d12caf
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpAutoLinkAuthenticatorFactory.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.List;
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:Ryan.Slominski@gmail.com">Ryan Slominski</a>
+ */
+public class IdpAutoLinkAuthenticatorFactory implements AuthenticatorFactory {
+ public static final String PROVIDER_ID = "idp-auto-link";
+ static IdpAutoLinkAuthenticator SINGLETON = new IdpAutoLinkAuthenticator();
+
+ @Override
+ public Authenticator create(KeycloakSession session) {
+ return SINGLETON;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String getReferenceCategory() {
+ return "autoLink";
+ }
+
+ @Override
+ public boolean isConfigurable() {
+ return false;
+ }
+
+ public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+ AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+ AuthenticationExecutionModel.Requirement.REQUIRED,
+ AuthenticationExecutionModel.Requirement.DISABLED};
+
+ @Override
+ public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+ return REQUIREMENT_CHOICES;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Automatically link brokered account";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Automatically link brokered account without any verification";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return null;
+ }
+
+ @Override
+ public boolean isUserSetupAllowed() {
+ return false;
+ }
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
index 4ecc8ff..121bd9e 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -33,6 +33,7 @@ org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthentic
org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFactory
org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory
org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory
+org.keycloak.authentication.authenticators.broker.IdpAutoLinkAuthenticatorFactory
org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticatorFactory
org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticatorFactory
org.keycloak.authentication.authenticators.x509.X509ClientCertificateAuthenticatorFactory
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
index b078e15..68359aa 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
@@ -166,6 +166,7 @@ public class ProvidersTest extends AbstractAuthenticationTest {
"You will be approved if you send query string parameter 'foo' with expected value.");
addProviderInfo(result, "http-basic-authenticator", "HTTP Basic Authentication", "Validates username and password from Authorization HTTP header");
addProviderInfo(result, "identity-provider-redirector", "Identity Provider Redirector", "Redirects to default Identity Provider or Identity Provider specified with kc_idp_hint query parameter");
+ addProviderInfo(result, "idp-auto-link", "Automatically link brokered account", "Automatically link brokered account without any verification");
addProviderInfo(result, "idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants " +
"to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict");
addProviderInfo(result, "idp-create-user-if-unique", "Create User If Unique", "Detect if there is existing Keycloak account " +
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java
index 6839568..c44bbac 100644
--- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java
@@ -39,6 +39,9 @@ import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.IdentityProviderModel;
+import static org.keycloak.testsuite.broker.AbstractFirstBrokerLoginTest.APP_REALM_ID;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -147,6 +150,77 @@ public class OIDCFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
}, APP_REALM_ID);
}
+ /**
+ * Tests that user can link federated identity with existing brokered
+ * account without prompt (KEYCLOAK-7270).
+ */
+ @Test
+ public void testAutoLinkAccountWithBroker() throws Exception {
+ final String originalFirstBrokerLoginFlowId = getRealm().getIdentityProviderByAlias(getProviderId()).getFirstBrokerLoginFlowId();
+
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ AuthenticationFlowModel newFlow = new AuthenticationFlowModel();
+ newFlow.setAlias("AutoLink");
+ newFlow.setDescription("AutoLink");
+ newFlow.setProviderId("basic-flow");
+ newFlow.setBuiltIn(false);
+ newFlow.setTopLevel(true);
+ newFlow = appRealm.addAuthenticationFlow(newFlow);
+
+ AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
+ execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+ execution.setAuthenticatorFlow(false);
+ execution.setAuthenticator("idp-create-user-if-unique");
+ execution.setPriority(1);
+ execution.setParentFlow(newFlow.getId());
+ execution = appRealm.addAuthenticatorExecution(execution);
+
+ AuthenticationExecutionModel execution2 = new AuthenticationExecutionModel();
+ execution2.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+ execution2.setAuthenticatorFlow(false);
+ execution2.setAuthenticator("idp-auto-link");
+ execution2.setPriority(2);
+ execution2.setParentFlow(newFlow.getId());
+ execution2 = appRealm.addAuthenticatorExecution(execution2);
+
+ IdentityProviderModel idp = appRealm.getIdentityProviderByAlias(getProviderId());
+ idp.setFirstBrokerLoginFlowId(newFlow.getId());
+ appRealm.updateIdentityProvider(idp);
+
+ }
+ }, APP_REALM_ID);
+
+ // login through OIDC broker
+ loginIDP("pedroigor");
+
+ // authenticated and redirected to app. User is linked with identity provider
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+ UserModel federatedUser = getFederatedUser();
+
+ assertNotNull(federatedUser);
+ assertEquals("pedroigor", federatedUser.getUsername());
+ assertEquals("psilva@redhat.com", federatedUser.getEmail());
+
+ RealmModel realmWithBroker = getRealm();
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realmWithBroker);
+ assertEquals(1, federatedIdentities.size());
+
+ for (FederatedIdentityModel link : federatedIdentities) {
+ Assert.assertEquals("pedroigor", link.getUserName());
+ Assert.assertTrue(link.getIdentityProvider().equals(getProviderId()));
+ }
+
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ IdentityProviderModel idp = appRealm.getIdentityProviderByAlias(getProviderId());
+ idp.setFirstBrokerLoginFlowId(originalFirstBrokerLoginFlowId);
+ appRealm.updateIdentityProvider(idp);
+ }
+ }, APP_REALM_ID);
+ }
// KEYCLOAK-5936
@Test