keycloak-memoizeit

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