From ee801c2c328f6823c616cd02dc222fa8ff9862d0 Mon Sep 17 00:00:00 2001 From: vramik Date: Mon, 9 Nov 2015 15:03:17 +0100 Subject: [PATCH 1/3] arquillian-testsuite: KEYCLOAK-2046 --- .../KeycloakArquillianExtension.java | 2 - .../arquillian/jira/JBossJiraParser.java | 43 ------------- .../testsuite/arquillian/jira/Jira.java | 28 --------- .../jira/JiraTestExecutionDecider.java | 61 ------------------- .../testsuite/arquillian/jira/Status.java | 36 ----------- .../AbstractCorsExampleAdapterTest.java | 2 - .../AbstractDemoServletsAdapterTest.java | 5 -- .../AbstractSessionServletAdapterTest.java | 5 -- .../console/authentication/OTPPolicyTest.java | 2 - 9 files changed, 184 deletions(-) delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JBossJiraParser.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Jira.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JiraTestExecutionDecider.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Status.java diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java index 5c82c3f551..287d516f6d 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java @@ -9,7 +9,6 @@ import org.jboss.arquillian.core.spi.LoadableExtension; import org.jboss.arquillian.graphene.location.CustomizableURLResourceProvider; import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider; import org.jboss.arquillian.test.spi.execution.TestExecutionDecider; -import org.keycloak.testsuite.arquillian.jira.JiraTestExecutionDecider; import org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer; import org.keycloak.testsuite.arquillian.migration.MigrationTestExecutionDecider; import org.keycloak.testsuite.arquillian.undertow.CustomUndertowContainer; @@ -39,7 +38,6 @@ public class KeycloakArquillianExtension implements LoadableExtension { .service(DeployableContainer.class, CustomKarafContainer.class); builder - //.service(TestExecutionDecider.class, JiraTestExecutionDecider.class) .service(TestExecutionDecider.class, MigrationTestExecutionDecider.class); builder diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JBossJiraParser.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JBossJiraParser.java deleted file mode 100644 index adf513db30..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JBossJiraParser.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.keycloak.testsuite.arquillian.jira; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.MediaType; - -/** - * - * @author Petr Mensik - */ -public class JBossJiraParser { - - private static final String JBOSS_TRACKER_REST_URL = "https://issues.jboss.org/rest/api/latest/issue/"; - - public static boolean isIssueClosed(String issueId) { - Status issueStatus; - try { - issueStatus = getIssueStatus(issueId); - } catch (Exception e) { - issueStatus = Status.CLOSED; //let the test run in case there is no connection - } - return issueStatus == Status.CLOSED || issueStatus == Status.RESOLVED; - } - - private static Status getIssueStatus(String issueId) throws Exception { - Client client = ClientBuilder.newClient(); - WebTarget target = client.target(JBOSS_TRACKER_REST_URL); - String json = target.path(issueId).request().accept(MediaType.APPLICATION_JSON_TYPE).get(String.class); - JsonObject jsonObject = new Gson().fromJson(json, JsonElement.class).getAsJsonObject(); - String status = jsonObject.getAsJsonObject("fields").getAsJsonObject("status").get("name").getAsString(); - client.close(); - return Status.getByStatus(status); - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Jira.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Jira.java deleted file mode 100644 index 52b69b5553..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Jira.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.keycloak.testsuite.arquillian.jira; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Value should contain name of the issue listed in JBoss JIRA (like - * KEYCLOAK-1234), it can also contain multiple names separated by coma. - * - * @author Petr Mensik - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD}) -@Documented -public @interface Jira { - - String value(); - boolean enabled() default true; -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JiraTestExecutionDecider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JiraTestExecutionDecider.java deleted file mode 100644 index 0ce98c5435..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JiraTestExecutionDecider.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.keycloak.testsuite.arquillian.jira; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import org.jboss.arquillian.test.spi.execution.ExecutionDecision; -import org.jboss.arquillian.test.spi.execution.TestExecutionDecider; - -import static org.keycloak.testsuite.arquillian.jira.JBossJiraParser.isIssueClosed; - -/** - * - * @author Petr Mensik - */ -public class JiraTestExecutionDecider implements TestExecutionDecider { - - private static Map cache = new HashMap<>(); - - @Override - public ExecutionDecision decide(Method method) { - Jira jiraAnnotation = method.getAnnotation(Jira.class); - if (jiraAnnotation != null && jiraAnnotation.enabled()) { - boolean executeTest = true; - String[] issueIds = getIssuesId(jiraAnnotation.value()); - for (String issueId : issueIds) { - if (cache.containsKey(issueId)) { - executeTest = cache.get(issueId); - } else { - if (isIssueClosed(issueId)) { - cache.put(issueId, true); - } else { - executeTest = false; - cache.put(issueId, false); - } - } - } - - if (executeTest) { - return ExecutionDecision.execute(); - } else { - return ExecutionDecision.dontExecute("Issue is still opened, therefore skipping the test " + method.getName()); - } - } - return ExecutionDecision.execute(); - } - - private String[] getIssuesId(String value) { - return value.replaceAll("\\s+", "").split(","); - } - - @Override - public int precedence() { - return 0; - } - -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Status.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Status.java deleted file mode 100644 index 47f707a1e4..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Status.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.keycloak.testsuite.arquillian.jira; - -/** - * - * @author Petr Mensik - */ -public enum Status { - - OPEN("Open"), CLOSED("Closed"), PULL_REQUEST_SENT("Pull Request Sent"), REOPENED("Reopened"), - RESOLVED("Resolved"), CODING_IN_PROGRESS("Coding In Progress "); - - private String status; - - private Status(String status) { - this.status = status; - } - - public String getStatus() { - return status; - } - - public static Status getByStatus(String status) { - for (Status s : Status.values()) { - if (s.getStatus().equals(status)) { - return s; - } - } - return null; - } - -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java index 72db4bb163..24933cb9da 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java @@ -10,7 +10,6 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest; import org.keycloak.testsuite.adapter.page.AngularCorsProductExample; import org.keycloak.testsuite.adapter.page.CorsDatabaseServiceExample; -import org.keycloak.testsuite.arquillian.jira.Jira; import org.keycloak.testsuite.auth.page.account.Account; import java.io.File; @@ -63,7 +62,6 @@ public abstract class AbstractCorsExampleAdapterTest extends AbstractExampleAdap driver.manage().deleteAllCookies(); } - @Jira("KEYCLOAK-1546") @Test public void angularCorsProductTest() { angularCorsProductExample.navigateTo(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java index 9fb8165e3c..8a470ad79b 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java @@ -34,7 +34,6 @@ import org.keycloak.testsuite.adapter.page.CustomerPortal; import org.keycloak.testsuite.adapter.page.InputPortal; import org.keycloak.testsuite.adapter.page.ProductPortal; import org.keycloak.testsuite.adapter.page.SecurePortal; -import org.keycloak.testsuite.arquillian.jira.Jira; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf; import org.keycloak.util.BasicAuthHelper; @@ -224,7 +223,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd } @Test - @Jira(value = "KEYCLOAK-1478") // rejected public void testLoginSSOIdleRemoveExpiredUserSessions() { // test login to customer-portal which does a bearer request to customer-db customerPortal.navigateTo(); @@ -279,7 +277,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd testRealmResource().update(demoRealmRep); } - @Jira("KEYCLOAK-518") @Test public void testNullBearerToken() { Client client = ClientBuilder.newClient(); @@ -293,7 +290,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd client.close(); } - @Jira("KEYCLOAK-1368") @Test public void testNullBearerTokenCustomErrorPage() { Client client = ClientBuilder.newClient(); @@ -326,7 +322,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd client.close(); } - @Jira("KEYCLOAK-518") @Test public void testBadUser() { Client client = ClientBuilder.newClient(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java index 79c0befd18..cd969a36fa 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java @@ -16,7 +16,6 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.adapter.page.SessionPortal; -import org.keycloak.testsuite.arquillian.jira.Jira; import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO; import org.keycloak.testsuite.auth.page.account.Sessions; import org.keycloak.testsuite.auth.page.login.Login; @@ -60,7 +59,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets @SecondBrowser protected WebDriver driver2; - @Jira("KEYCLOAK-732") @Test public void testSingleSessionInvalidated() { @@ -102,7 +100,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets } @Test - @Jira("KEYCLOAK-741, KEYCLOAK-1485") public void testSessionInvalidatedAfterFailedRefresh() { RealmRepresentation testRealmRep = testRealmResource().toRepresentation(); ClientResource sessionPortalRes = null; @@ -139,7 +136,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets } @Test - @Jira("KEYCLOAK-942") public void testAdminApplicationLogout() { // login as bburke loginAndCheckSession(driver, testRealmLoginPage); @@ -157,7 +153,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets } @Test - @Jira("KEYCLOAK-1216, KEYCLOAK-1485") public void testAccountManagementSessionsLogout() { // login as bburke loginAndCheckSession(driver, testRealmLoginPage); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java index 8e6359312a..72455e216a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java @@ -26,7 +26,6 @@ import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testsuite.arquillian.jira.Jira; import org.keycloak.testsuite.console.AbstractConsoleTest; import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicy; import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicyForm.Digits; @@ -67,7 +66,6 @@ public class OTPPolicyTest extends AbstractConsoleTest { } @Test - @Jira(value = "KEYCLOAK-2031") public void invalidValuesTest() { otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "", "30"); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); From 99e75c69a07c890196d6952483f378cea424a4db Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 9 Nov 2015 15:39:08 +0100 Subject: [PATCH 2/3] KEYCLOAK-1750 First broker login - support for change password after first social login --- .../utils/DefaultAuthenticationFlows.java | 2 +- ...> IdpCreateUserIfUniqueAuthenticator.java} | 12 +++++-- ...eateUserIfUniqueAuthenticatorFactory.java} | 34 ++++++++++++++----- ...ycloak.authentication.AuthenticatorFactory | 2 +- 4 files changed, 37 insertions(+), 13 deletions(-) rename services/src/main/java/org/keycloak/authentication/authenticators/broker/{IdpDetectDuplicationsAuthenticator.java => IdpCreateUserIfUniqueAuthenticator.java} (85%) rename services/src/main/java/org/keycloak/authentication/authenticators/broker/{IdpDetectDuplicationsAuthenticatorFactory.java => IdpCreateUserIfUniqueAuthenticatorFactory.java} (62%) diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index a88e805ef6..572d6f6b04 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -334,7 +334,7 @@ public class DefaultAuthenticationFlows { execution = new AuthenticationExecutionModel(); execution.setParentFlow(firstBrokerLogin.getId()); execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE); - execution.setAuthenticator("idp-detect-duplications"); + execution.setAuthenticator("idp-create-user-if-unique"); execution.setPriority(20); execution.setAuthenticatorFlow(false); realm.addAuthenticatorExecution(execution); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpDetectDuplicationsAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java similarity index 85% rename from services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpDetectDuplicationsAuthenticator.java rename to services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java index f060730f4d..ffb23004a3 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpDetectDuplicationsAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java @@ -10,17 +10,19 @@ import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo; import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.FormMessage; import org.keycloak.services.messages.Messages; /** * @author Marek Posolda */ -public class IdpDetectDuplicationsAuthenticator extends AbstractIdpAuthenticator { +public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator { - protected static Logger logger = Logger.getLogger(IdpDetectDuplicationsAuthenticator.class); + protected static Logger logger = Logger.getLogger(IdpCreateUserIfUniqueAuthenticator.class); @Override @@ -54,6 +56,12 @@ public class IdpDetectDuplicationsAuthenticator extends AbstractIdpAuthenticator federatedUser.setAttribute(attr.getKey(), attr.getValue()); } + AuthenticatorConfigModel config = context.getAuthenticatorConfig(); + if (config != null && Boolean.parseBoolean(config.getConfig().get(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION))) { + logger.debugf("User '%s' required to update password", federatedUser.getUsername()); + federatedUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); + } + // TODO: Event context.setUser(federatedUser); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpDetectDuplicationsAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticatorFactory.java similarity index 62% rename from services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpDetectDuplicationsAuthenticatorFactory.java rename to services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticatorFactory.java index 86a4dbd912..468e8cca86 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpDetectDuplicationsAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticatorFactory.java @@ -1,5 +1,6 @@ package org.keycloak.authentication.authenticators.broker; +import java.util.ArrayList; import java.util.List; import org.keycloak.Config; @@ -13,10 +14,12 @@ import org.keycloak.provider.ProviderConfigProperty; /** * @author Marek Posolda */ -public class IdpDetectDuplicationsAuthenticatorFactory implements AuthenticatorFactory { +public class IdpCreateUserIfUniqueAuthenticatorFactory implements AuthenticatorFactory { - public static final String PROVIDER_ID = "idp-detect-duplications"; - static IdpDetectDuplicationsAuthenticator SINGLETON = new IdpDetectDuplicationsAuthenticator(); + public static final String PROVIDER_ID = "idp-create-user-if-unique"; + static IdpCreateUserIfUniqueAuthenticator SINGLETON = new IdpCreateUserIfUniqueAuthenticator(); + + public static final String REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION = "require.password.update.after.registration"; @Override public Authenticator create(KeycloakSession session) { @@ -50,7 +53,7 @@ public class IdpDetectDuplicationsAuthenticatorFactory implements AuthenticatorF @Override public boolean isConfigurable() { - return false; + return true; } public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { @@ -73,13 +76,26 @@ public class IdpDetectDuplicationsAuthenticatorFactory implements AuthenticatorF return "Detect if there is existing Keycloak account with same email like identity provider. If no, create new user"; } - @Override - public List getConfigProperties() { - return null; - } - @Override public boolean isUserSetupAllowed() { return false; } + + private static final List configProperties = new ArrayList(); + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION); + property.setLabel("Require Password Update After Registration"); + property.setType(ProviderConfigProperty.BOOLEAN_TYPE); + property.setHelpText("If this option is true and new user is successfully imported from Identity Provider to Keycloak (there is no duplicated email or username detected in Keycloak DB), then this user is required to update his password"); + configProperties.add(property); + } + + + @Override + public List getConfigProperties() { + return configProperties; + } } 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 ec98dd22f8..6c900668cd 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 @@ -10,7 +10,7 @@ org.keycloak.authentication.authenticators.resetcred.ResetCredentialEmail org.keycloak.authentication.authenticators.resetcred.ResetOTP org.keycloak.authentication.authenticators.resetcred.ResetPassword org.keycloak.authentication.authenticators.broker.IdpUpdateProfileAuthenticatorFactory -org.keycloak.authentication.authenticators.broker.IdpDetectDuplicationsAuthenticatorFactory +org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFactory org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory From 4ca442d1b20269dd1e733f7672016895d1f51ba3 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 9 Nov 2015 22:09:37 +0100 Subject: [PATCH 3/3] KEYCLOAK-1750 Option updateProfileOnFirstLogin moved from IdentityProvider to IdpReviewProfile authenticator --- .../META-INF/jpa-changelog-1.7.0.xml | 1 + .../idm/IdentityProviderRepresentation.java | 7 +- .../modules/MigrationFromOlderVersions.xml | 30 ++++- .../theme/base/admin/resources/js/app.js | 10 +- .../admin/resources/js/controllers/realm.js | 23 ++-- .../partials/authentication-flows.html | 4 +- .../partials/authenticator-config.html | 1 + .../realm-identity-provider-oidc.html | 15 +-- .../realm-identity-provider-saml.html | 13 --- .../realm-identity-provider-social.html | 15 +-- .../base/login/login-idp-link-confirm.ftl | 2 +- .../login/messages/messages_en.properties | 2 +- .../models/IdentityProviderModel.java | 23 ---- .../entities/IdentityProviderEntity.java | 9 -- .../utils/DefaultAuthenticationFlows.java | 28 ++++- .../models/utils/ModelToRepresentation.java | 1 - .../models/utils/RepresentationToModel.java | 1 - .../org/keycloak/models/jpa/RealmAdapter.java | 3 - .../jpa/entities/IdentityProviderEntity.java | 11 -- .../mongo/keycloak/adapters/RealmAdapter.java | 3 - ...ava => IdpReviewProfileAuthenticator.java} | 18 ++- .../IdpReviewProfileAuthenticatorFactory.java | 107 ++++++++++++++++++ .../IdpUpdateProfileAuthenticatorFactory.java | 84 -------------- ...ycloak.authentication.AuthenticatorFactory | 2 +- .../testsuite/admin/IdentityProviderTest.java | 1 - .../broker/AbstractIdentityProviderTest.java | 44 +++++-- .../broker/ImportIdentityProviderTest.java | 30 ++--- .../broker-test/test-realm-with-broker.json | 11 -- 28 files changed, 240 insertions(+), 259 deletions(-) rename services/src/main/java/org/keycloak/authentication/authenticators/broker/{IdpUpdateProfileAuthenticator.java => IdpReviewProfileAuthenticator.java} (83%) create mode 100644 services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java delete mode 100644 services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUpdateProfileAuthenticatorFactory.java diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml index 4f7d5fcd9f..c5d7d5252d 100644 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml @@ -7,6 +7,7 @@ + \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java index 864d255dc0..0dc7f0a019 100755 --- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java @@ -46,6 +46,7 @@ public class IdentityProviderRepresentation { * @see #UPFLM_MISSING * @see #UPFLM_OFF */ + @Deprecated protected String updateProfileFirstLoginMode = UPFLM_ON; protected boolean trustEmail; @@ -107,15 +108,17 @@ public class IdentityProviderRepresentation { } /** - * @return see {@link #updateProfileFirstLoginMode} + * @deprecated deprecated and replaced by configuration on IdpReviewProfileAuthenticator */ + @Deprecated public String getUpdateProfileFirstLoginMode() { return updateProfileFirstLoginMode; } /** - * @param updateProfileFirstLoginMode see {@link #updateProfileFirstLoginMode} + * @deprecated deprecated and replaced by configuration on IdpReviewProfileAuthenticator */ + @Deprecated public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) { this.updateProfileFirstLoginMode = updateProfileFirstLoginMode; } diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml index 650ed769bd..b0a443dd41 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml @@ -79,16 +79,34 @@
Version specific migration +
+ Migrating to 1.7.0.CR1 + + Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator + + In this version, we added First Broker Login, which allows you to specify what exactly should be done + when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user + yet linked to the social account. As part of this work, we added option First Login Flow to identity providers where + you can specify the flow and then you can configure this flow under Authentication tab in admin console. + + + We also removed the option Update Profile On First Login from the Identity provider settings and moved it + to the configuration of Review Profile authenticator. So once you specify which flow should be used for your + Identity provider (by default it's First Broker Login flow), you go to Authentication tab, select the flow + and then you configure the option under Review Profile authenticator. + + +
Migrating to 1.6.0.Final - Refresh tokens are not reusable anymore + Option that refresh tokens are not reusable anymore - Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak no longer permits - this by default. When a refresh token is used to obtain a new access token a new refresh token is also - included. This new refresh token should be used next time the access token is refreshed. If this is - a problem for you it's possible to enable reuse of refresh tokens in the admin console under token - settings. + Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak still permits this, + but also have an option Revoke refresh token to disallow it. Option is in in admin console under token settings. + When a refresh token is used to obtain a new access token a new refresh token is also + included. When option is enabled, then this new refresh token should be used next time the access token is refreshed. + It won't be possible to reuse old refresh token multiple times. diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index 96ee94d8aa..7fbbc808e1 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -1348,12 +1348,15 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'RealmOtpPolicyCtrl' }) - .when('/realms/:realm/authentication/config/:provider/:config', { + .when('/realms/:realm/authentication/flows/:flow/config/:provider/:config', { templateUrl : resourceUrl + '/partials/authenticator-config.html', resolve : { realm : function(RealmLoader) { return RealmLoader(); }, + flow : function(AuthenticationFlowLoader) { + return AuthenticationFlowLoader(); + }, configType : function(AuthenticationConfigDescriptionLoader) { return AuthenticationConfigDescriptionLoader(); }, @@ -1363,12 +1366,15 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'AuthenticationConfigCtrl' }) - .when('/create/authentication/:realm/execution/:executionId/provider/:provider', { + .when('/create/authentication/:realm/flows/:flow/execution/:executionId/provider/:provider', { templateUrl : resourceUrl + '/partials/authenticator-config.html', resolve : { realm : function(RealmLoader) { return RealmLoader(); }, + flow : function(AuthenticationFlowLoader) { + return AuthenticationFlowLoader(); + }, configType : function(AuthenticationConfigDescriptionLoader) { return AuthenticationConfigDescriptionLoader(); }, diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 37ec3abed2..e21b50ef83 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -599,15 +599,6 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload $scope.realm = angular.copy(realm); - $scope.initProvider = function() { - if (instance && instance.alias) { - - } else { - $scope.identityProvider.updateProfileFirstLoginMode = "on"; - } - - }; - $scope.initSamlProvider = function() { $scope.nameIdFormats = [ /* @@ -658,7 +649,6 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload } else { $scope.identityProvider.config.nameIDPolicyFormat = $scope.nameIdFormats[0].format; $scope.identityProvider.config.signatureAlgorithm = $scope.signatureAlgorithms[1]; - $scope.identityProvider.updateProfileFirstLoginMode = "off"; } } @@ -676,7 +666,6 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload $scope.identityProvider.alias = providerFactory.id; $scope.identityProvider.providerId = providerFactory.id; $scope.identityProvider.enabled = true; - $scope.identityProvider.updateProfileFirstLoginMode = "off"; $scope.identityProvider.authenticateByDefault = false; $scope.identityProvider.firstBrokerLoginFlowAlias = 'first broker login'; $scope.newIdentityProvider = true; @@ -1909,8 +1898,9 @@ module.controller('RequiredActionsCtrl', function($scope, realm, unregisteredReq }); -module.controller('AuthenticationConfigCtrl', function($scope, realm, configType, config, AuthenticationConfig, Notifications, Dialog, $location) { +module.controller('AuthenticationConfigCtrl', function($scope, realm, flow, configType, config, AuthenticationConfig, Notifications, Dialog, $location) { $scope.realm = realm; + $scope.flow = flow; $scope.configType = configType; $scope.create = false; $scope.config = angular.copy(config); @@ -1935,7 +1925,7 @@ module.controller('AuthenticationConfigCtrl', function($scope, realm, configType }, $scope.config, function() { $scope.changed = false; config = angular.copy($scope.config); - $location.url("/realms/" + realm.realm + '/authentication/config/' + configType.providerId + "/" + config.id); + $location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + config.id); Notifications.success("Your changes have been saved."); }); }; @@ -1954,15 +1944,16 @@ module.controller('AuthenticationConfigCtrl', function($scope, realm, configType Dialog.confirmDelete($scope.config.alias, 'config', function() { AuthenticationConfig.remove({ realm: realm.realm, config : $scope.config.id }, function() { Notifications.success("The config has been deleted."); - $location.url("/realms/" + realm.realm + '/authentication/flows'); + $location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id); }); }); }; }); -module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, configType, execution, AuthenticationExecutionConfig, Notifications, Dialog, $location) { +module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, flow, configType, execution, AuthenticationExecutionConfig, Notifications, Dialog, $location) { $scope.realm = realm; + $scope.flow = flow; $scope.create = true; $scope.config = { config: {}}; $scope.configType = configType; @@ -1980,7 +1971,7 @@ module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, conf }, $scope.config, function(data, headers) { var l = headers().location; var id = l.substring(l.lastIndexOf("/") + 1); - var url = "/realms/" + realm.realm + '/authentication/config/' + configType.providerId + "/" + id; + var url = "/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + id; console.log('redirect url: ' + url); $location.url(url); Notifications.success("Config has been created."); diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html index e055978bd6..a77cf9f64d 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html @@ -53,8 +53,8 @@
  • Delete
  • Add Execution
  • Add Flow
  • -
  • Config
  • -
  • Config
  • +
  • Config
  • +
  • Config
  • diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html index 585680616b..f5875683e7 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html @@ -2,6 +2,7 @@ diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index 317709eac2..4cbd12a09f 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -1,4 +1,4 @@ -
    +
    {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}
    -
    - -
    -
    - -
    -
    - {{:: 'update-profile-on-first-login.tooltip' | translate}} -
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html index 53cbf3a159..c8f6c3771a 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html @@ -52,19 +52,6 @@
    {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}
    -
    - -
    -
    - -
    -
    - {{:: 'update-profile-on-first-login.tooltip' | translate}} -
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html index a4e3f1331b..5897f9972b 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html @@ -1,4 +1,4 @@ -
    +
    {{:: 'identity-provider.enabled.tooltip' | translate}}
    -
    - -
    -
    - -
    -
    - {{:: 'update-profile-on-first-login.tooltip' | translate}} -
    diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl index 28b12d0789..02923d97b5 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl +++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl @@ -12,7 +12,7 @@
    - +
    diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties index ca18e21f7d..803ef2dcc0 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -142,7 +142,7 @@ federatedIdentityExistsMessage=User with {0} {1} already exists. Please login to confirmLinkIdpTitle=Account already exists federatedIdentityConfirmLinkMessage=User with {0} {1} already exists. How do you want to continue? federatedIdentityConfirmReauthenticateMessage=Authenticate as {0} to link your account with {1} -confirmLinkIdpUpdateProfile=Update profile info +confirmLinkIdpReviewProfile=Review profile info confirmLinkIdpContinue=Link {0} with existing account configureTotpMessage=You need to set up Mobile Authenticator to activate your account. diff --git a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java index 288b1f0369..862f7239a9 100755 --- a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java +++ b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java @@ -45,14 +45,6 @@ public class IdentityProviderModel implements Serializable { private String providerId; private boolean enabled; - - /** - * For possible values see {@link IdentityProviderRepresentation#getUpdateProfileFirstLoginMode()} - * @see IdentityProviderRepresentation#UPFLM_ON - * @see IdentityProviderRepresentation#UPFLM_MISSING - * @see IdentityProviderRepresentation#UPFLM_OFF - */ - protected String updateProfileFirstLoginMode = IdentityProviderRepresentation.UPFLM_ON; private boolean trustEmail; @@ -81,7 +73,6 @@ public class IdentityProviderModel implements Serializable { this.alias = model.getAlias(); this.config = new HashMap(model.getConfig()); this.enabled = model.isEnabled(); - this.updateProfileFirstLoginMode = model.getUpdateProfileFirstLoginMode(); this.trustEmail = model.isTrustEmail(); this.storeToken = model.isStoreToken(); this.authenticateByDefault = model.isAuthenticateByDefault(); @@ -121,20 +112,6 @@ public class IdentityProviderModel implements Serializable { this.enabled = enabled; } - /** - * @see IdentityProviderRepresentation#getUpdateProfileFirstLoginMode() - */ - public String getUpdateProfileFirstLoginMode() { - return updateProfileFirstLoginMode; - } - - /** - * @see IdentityProviderRepresentation#setUpdateProfileFirstLoginMode(String) - */ - public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) { - this.updateProfileFirstLoginMode = updateProfileFirstLoginMode; - } - public boolean isStoreToken() { return this.storeToken; } diff --git a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java index 4ea6602ae1..f92f1d29c0 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java @@ -30,7 +30,6 @@ public class IdentityProviderEntity { private String providerId; private String name; private boolean enabled; - private String updateProfileFirstLoginMode; private boolean trustEmail; private boolean storeToken; protected boolean addReadTokenRoleOnCreate; @@ -63,14 +62,6 @@ public class IdentityProviderEntity { this.enabled = enabled; } - public String getUpdateProfileFirstLoginMode() { - return updateProfileFirstLoginMode; - } - - public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) { - this.updateProfileFirstLoginMode = updateProfileFirstLoginMode; - } - public boolean isAuthenticateByDefault() { return authenticateByDefault; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index 572d6f6b04..a874f5eecb 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -1,9 +1,14 @@ package org.keycloak.models.utils; +import java.util.HashMap; +import java.util.Map; + import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; +import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; +import org.keycloak.representations.idm.IdentityProviderRepresentation; /** * @author Bill Burke @@ -21,6 +26,8 @@ public class DefaultAuthenticationFlows { public static final String CLIENT_AUTHENTICATION_FLOW = "clients"; public static final String FIRST_BROKER_LOGIN_FLOW = "first broker login"; + public static final String IDP_REVIEW_PROFILE_CONFIG_ALIAS = "review profile config"; + public static void addFlows(RealmModel realm) { if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm); if (realm.getFlowByAlias(DIRECT_GRANT_FLOW) == null) directGrantFlow(realm, false); @@ -321,24 +328,41 @@ public class DefaultAuthenticationFlows { firstBrokerLogin.setTopLevel(true); firstBrokerLogin.setBuiltIn(true); firstBrokerLogin = realm.addAuthenticationFlow(firstBrokerLogin); - // realm.setClientAuthenticationFlow(clients); + + AuthenticatorConfigModel reviewProfileConfig = new AuthenticatorConfigModel(); + reviewProfileConfig.setAlias(IDP_REVIEW_PROFILE_CONFIG_ALIAS); + Map config = new HashMap<>(); + config.put("update.profile.on.first.login", IdentityProviderRepresentation.UPFLM_MISSING); + reviewProfileConfig.setConfig(config); + reviewProfileConfig = realm.addAuthenticatorConfig(reviewProfileConfig); AuthenticationExecutionModel execution = new AuthenticationExecutionModel(); execution.setParentFlow(firstBrokerLogin.getId()); execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED); - execution.setAuthenticator("idp-update-profile"); + execution.setAuthenticator("idp-review-profile"); execution.setPriority(10); execution.setAuthenticatorFlow(false); + execution.setAuthenticatorConfig(reviewProfileConfig.getId()); realm.addAuthenticatorExecution(execution); + + AuthenticatorConfigModel createUserIfUniqueConfig = new AuthenticatorConfigModel(); + createUserIfUniqueConfig.setAlias("create unique user config"); + config = new HashMap<>(); + config.put("require.password.update.after.registration", "false"); + createUserIfUniqueConfig.setConfig(config); + createUserIfUniqueConfig = realm.addAuthenticatorConfig(createUserIfUniqueConfig); + execution = new AuthenticationExecutionModel(); execution.setParentFlow(firstBrokerLogin.getId()); execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE); execution.setAuthenticator("idp-create-user-if-unique"); execution.setPriority(20); execution.setAuthenticatorFlow(false); + execution.setAuthenticatorConfig(createUserIfUniqueConfig.getId()); realm.addAuthenticatorExecution(execution); + AuthenticationFlowModel linkExistingAccountFlow = new AuthenticationFlowModel(); linkExistingAccountFlow.setTopLevel(false); linkExistingAccountFlow.setBuiltIn(true); diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index b975b56052..69fcc07799 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -389,7 +389,6 @@ public class ModelToRepresentation { providerRep.setAlias(identityProviderModel.getAlias()); providerRep.setEnabled(identityProviderModel.isEnabled()); providerRep.setStoreToken(identityProviderModel.isStoreToken()); - providerRep.setUpdateProfileFirstLoginMode(identityProviderModel.getUpdateProfileFirstLoginMode()); providerRep.setTrustEmail(identityProviderModel.isTrustEmail()); providerRep.setAuthenticateByDefault(identityProviderModel.isAuthenticateByDefault()); providerRep.setConfig(identityProviderModel.getConfig()); diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 110b376fde..a31d35592b 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -1080,7 +1080,6 @@ public class RepresentationToModel { identityProviderModel.setAlias(representation.getAlias()); identityProviderModel.setProviderId(representation.getProviderId()); identityProviderModel.setEnabled(representation.isEnabled()); - identityProviderModel.setUpdateProfileFirstLoginMode(representation.getUpdateProfileFirstLoginMode()); identityProviderModel.setTrustEmail(representation.isTrustEmail()); identityProviderModel.setAuthenticateByDefault(representation.isAuthenticateByDefault()); identityProviderModel.setStoreToken(representation.isStoreToken()); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 1cd8b45fde..106eaf8b1d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -1216,7 +1216,6 @@ public class RealmAdapter implements RealmModel { identityProviderModel.setInternalId(entity.getInternalId()); identityProviderModel.setConfig(entity.getConfig()); identityProviderModel.setEnabled(entity.isEnabled()); - identityProviderModel.setUpdateProfileFirstLoginMode(entity.getUpdateProfileFirstLoginMode()); identityProviderModel.setTrustEmail(entity.isTrustEmail()); identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault()); identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId()); @@ -1250,7 +1249,6 @@ public class RealmAdapter implements RealmModel { entity.setEnabled(identityProvider.isEnabled()); entity.setStoreToken(identityProvider.isStoreToken()); entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate()); - entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode()); entity.setTrustEmail(identityProvider.isTrustEmail()); entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault()); entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId()); @@ -1278,7 +1276,6 @@ public class RealmAdapter implements RealmModel { if (entity.getInternalId().equals(identityProvider.getInternalId())) { entity.setAlias(identityProvider.getAlias()); entity.setEnabled(identityProvider.isEnabled()); - entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode()); entity.setTrustEmail(identityProvider.isTrustEmail()); entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault()); entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId()); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java index 5071c9b8be..6bbc31c614 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java @@ -42,9 +42,6 @@ public class IdentityProviderEntity { @Column(name="ENABLED") private boolean enabled; - @Column(name = "UPDATE_PROFILE_FIRST_LGN_MD") - private String updateProfileFirstLoginMode; - @Column(name = "TRUST_EMAIL") private boolean trustEmail; @@ -106,14 +103,6 @@ public class IdentityProviderEntity { this.enabled = enabled; } - public String getUpdateProfileFirstLoginMode() { - return updateProfileFirstLoginMode; - } - - public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) { - this.updateProfileFirstLoginMode = updateProfileFirstLoginMode; - } - public boolean isStoreToken() { return this.storeToken; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 479862c01d..989497768c 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -823,7 +823,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme identityProviderModel.setInternalId(entity.getInternalId()); identityProviderModel.setConfig(entity.getConfig()); identityProviderModel.setEnabled(entity.isEnabled()); - identityProviderModel.setUpdateProfileFirstLoginMode(entity.getUpdateProfileFirstLoginMode()); identityProviderModel.setTrustEmail(entity.isTrustEmail()); identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault()); identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId()); @@ -855,7 +854,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme entity.setAlias(identityProvider.getAlias()); entity.setProviderId(identityProvider.getProviderId()); entity.setEnabled(identityProvider.isEnabled()); - entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode()); entity.setTrustEmail(identityProvider.isTrustEmail()); entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate()); entity.setStoreToken(identityProvider.isStoreToken()); @@ -884,7 +882,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme if (entity.getInternalId().equals(identityProvider.getInternalId())) { entity.setAlias(identityProvider.getAlias()); entity.setEnabled(identityProvider.isEnabled()); - entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode()); entity.setTrustEmail(identityProvider.isTrustEmail()); entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault()); entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId()); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUpdateProfileAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java similarity index 83% rename from services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUpdateProfileAuthenticator.java rename to services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java index 35e10fd247..04eb77b520 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUpdateProfileAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java @@ -14,6 +14,7 @@ import org.keycloak.events.Details; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -26,9 +27,9 @@ import org.keycloak.services.validation.Validation; /** * @author Marek Posolda */ -public class IdpUpdateProfileAuthenticator extends AbstractIdpAuthenticator { +public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator { - protected static Logger logger = Logger.getLogger(IdpUpdateProfileAuthenticator.class); + protected static Logger logger = Logger.getLogger(IdpReviewProfileAuthenticator.class); @Override public boolean requiresUser() { @@ -61,10 +62,17 @@ public class IdpUpdateProfileAuthenticator extends AbstractIdpAuthenticator { return true; } - IdentityProviderModel idpConfig = brokerContext.getIdpConfig(); + String updateProfileFirstLogin; + AuthenticatorConfigModel authenticatorConfig = context.getAuthenticatorConfig(); + if (authenticatorConfig == null || !authenticatorConfig.getConfig().containsKey(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN)) { + updateProfileFirstLogin = IdentityProviderRepresentation.UPFLM_MISSING; + } else { + updateProfileFirstLogin = authenticatorConfig.getConfig().get(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN); + } + RealmModel realm = context.getRealm(); - return IdentityProviderRepresentation.UPFLM_ON.equals(idpConfig.getUpdateProfileFirstLoginMode()) - || (IdentityProviderRepresentation.UPFLM_MISSING.equals(idpConfig.getUpdateProfileFirstLoginMode()) && !Validation.validateUserMandatoryFields(realm, userCtx)); + return IdentityProviderRepresentation.UPFLM_ON.equals(updateProfileFirstLogin) + || (IdentityProviderRepresentation.UPFLM_MISSING.equals(updateProfileFirstLogin) && !Validation.validateUserMandatoryFields(realm, userCtx)); } @Override diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java new file mode 100644 index 0000000000..e10c924c5a --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java @@ -0,0 +1,107 @@ +package org.keycloak.authentication.authenticators.broker; + +import java.util.ArrayList; +import java.util.Arrays; +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; +import org.keycloak.representations.idm.IdentityProviderRepresentation; + +/** + * @author Marek Posolda + */ +public class IdpReviewProfileAuthenticatorFactory implements AuthenticatorFactory { + + public static final String PROVIDER_ID = "idp-review-profile"; + static IdpReviewProfileAuthenticator SINGLETON = new IdpReviewProfileAuthenticator(); + + public static final String UPDATE_PROFILE_ON_FIRST_LOGIN = "update.profile.on.first.login"; + + @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 "reviewProfile"; + } + + @Override + public boolean isConfigurable() { + return true; + } + + public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { + AuthenticationExecutionModel.Requirement.REQUIRED, + AuthenticationExecutionModel.Requirement.DISABLED}; + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return REQUIREMENT_CHOICES; + } + + @Override + public String getDisplayType() { + return "Review Profile"; + } + + @Override + public String getHelpText() { + return "User reviews and updates profile data retrieved from Identity Provider in the displayed form"; + } + + @Override + public boolean isUserSetupAllowed() { + return false; + } + + private static final List configProperties = new ArrayList(); + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(UPDATE_PROFILE_ON_FIRST_LOGIN); + property.setLabel("{{:: 'update-profile-on-first-login' | translate}}"); + property.setType(ProviderConfigProperty.LIST_TYPE); + List updateProfileValues = Arrays.asList(IdentityProviderRepresentation.UPFLM_ON, IdentityProviderRepresentation.UPFLM_MISSING, IdentityProviderRepresentation.UPFLM_OFF); + property.setDefaultValue(updateProfileValues); + property.setHelpText("Define conditions under which a user has to review and update his profile after first-time login. Value 'On' means that" + + " page for reviewing profile will be displayed and user can review and update his profile. Value 'off' means that page won't be displayed." + + " Value 'missing' means that page is displayed just when some required attribute is missing (wasn't downloaded from identity provider). Value 'missing' is the default one"); + + configProperties.add(property); + } + + + @Override + public List getConfigProperties() { + return configProperties; + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUpdateProfileAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUpdateProfileAuthenticatorFactory.java deleted file mode 100644 index d947c60fca..0000000000 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUpdateProfileAuthenticatorFactory.java +++ /dev/null @@ -1,84 +0,0 @@ -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 Marek Posolda - */ -public class IdpUpdateProfileAuthenticatorFactory implements AuthenticatorFactory { - - public static final String PROVIDER_ID = "idp-update-profile"; - static IdpUpdateProfileAuthenticator SINGLETON = new IdpUpdateProfileAuthenticator(); - - @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 "updateProfile"; - } - - @Override - public boolean isConfigurable() { - return false; - } - - public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { - AuthenticationExecutionModel.Requirement.REQUIRED, - AuthenticationExecutionModel.Requirement.DISABLED}; - - @Override - public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { - return REQUIREMENT_CHOICES; - } - - @Override - public String getDisplayType() { - return "Update Profile"; - } - - @Override - public String getHelpText() { - return "Updates profile data retrieved from Identity Provider in the displayed form"; - } - - @Override - public List 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 6c900668cd..70551a17e9 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 @@ -9,7 +9,7 @@ org.keycloak.authentication.authenticators.resetcred.ResetCredentialChooseUser org.keycloak.authentication.authenticators.resetcred.ResetCredentialEmail org.keycloak.authentication.authenticators.resetcred.ResetOTP org.keycloak.authentication.authenticators.resetcred.ResetPassword -org.keycloak.authentication.authenticators.broker.IdpUpdateProfileAuthenticatorFactory +org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFactory org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java index a7bae96fa8..f88fb4ae48 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java @@ -52,7 +52,6 @@ public class IdentityProviderTest extends AbstractClientTest { assertEquals("clientSecret", representation.getConfig().get("clientSecret")); assertTrue(representation.isEnabled()); assertFalse(representation.isStoreToken()); - assertEquals(IdentityProviderRepresentation.UPFLM_ON, representation.getUpdateProfileFirstLoginMode()); assertFalse(representation.isTrustEmail()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java index f1d4e10936..8af43b2b63 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java @@ -23,15 +23,20 @@ import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory; +import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionTask; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.RequiredAction; +import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.IDToken; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.services.Urls; @@ -138,7 +143,7 @@ public abstract class AbstractIdentityProviderTest { @Test public void testSuccessfulAuthentication() { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true); Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile")); @@ -147,7 +152,7 @@ public abstract class AbstractIdentityProviderTest { @Test public void testSuccessfulAuthenticationUpdateProfileOnMissing_nothingMissing() { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_MISSING); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING); assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false); } @@ -155,7 +160,7 @@ public abstract class AbstractIdentityProviderTest { @Test public void testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail() { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_MISSING); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING); assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", "new@email.com", true); } @@ -163,7 +168,7 @@ public abstract class AbstractIdentityProviderTest { @Test public void testSuccessfulAuthenticationWithoutUpdateProfile() { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false); } @@ -182,7 +187,7 @@ public abstract class AbstractIdentityProviderTest { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); try { - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); identityProviderModel.setTrustEmail(false); UserModel federatedUser = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "test-user@localhost", false); @@ -251,7 +256,7 @@ public abstract class AbstractIdentityProviderTest { try { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null, false); @@ -268,12 +273,12 @@ public abstract class AbstractIdentityProviderTest { @Test public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled_emailTrustEnabled() { getRealm().setVerifyEmail(true); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); brokerServerRule.stopSession(this.session, true); this.session = brokerServerRule.startSession(); IdentityProviderModel identityProviderModel = getIdentityProviderModel(); try { - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF); identityProviderModel.setTrustEmail(true); UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false); @@ -300,7 +305,7 @@ public abstract class AbstractIdentityProviderTest { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); try { - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); identityProviderModel.setTrustEmail(true); UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", true); @@ -320,7 +325,7 @@ public abstract class AbstractIdentityProviderTest { try { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); authenticateWithIdentityProvider(identityProviderModel, "test-user", false); @@ -368,7 +373,7 @@ public abstract class AbstractIdentityProviderTest { try { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false); @@ -475,7 +480,7 @@ public abstract class AbstractIdentityProviderTest { public void testUserAlreadyExistsWhenNotUpdatingProfile() { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); this.driver.navigate().to("http://localhost:8081/test-app/"); @@ -509,6 +514,7 @@ public abstract class AbstractIdentityProviderTest { // Link my "pedroigor" identity with "test-user" from brokered Keycloak IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); accountFederatedIdentityPage.clickAddProvider(identityProviderModel.getAlias()); assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); @@ -609,6 +615,7 @@ public abstract class AbstractIdentityProviderTest { @Test public void testTokenStorageAndRetrievalByApplication() { + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); IdentityProviderModel identityProviderModel = getIdentityProviderModel(); identityProviderModel.setStoreToken(true); @@ -774,7 +781,6 @@ public abstract class AbstractIdentityProviderTest { assertNotNull(identityProviderModel); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON); identityProviderModel.setEnabled(true); return identityProviderModel; @@ -851,4 +857,18 @@ public abstract class AbstractIdentityProviderTest { return htmlVerificationUrl; } + + private void setUpdateProfileFirstLogin(final String updateProfileFirstLogin) { + KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), new KeycloakSessionTask() { + + @Override + public void run(KeycloakSession session) { + RealmModel realm = session.realms().getRealm("realm-with-broker"); + AuthenticatorConfigModel reviewProfileConfig = realm.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS); + reviewProfileConfig.getConfig().put(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN, updateProfileFirstLogin); + realm.updateAuthenticatorConfig(reviewProfileConfig); + } + + }); + } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java index b5bc056aa0..8e23f114d2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java @@ -81,7 +81,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes identityProviderModel.getConfig().put("config-added", "value-added"); identityProviderModel.setEnabled(false); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF); identityProviderModel.setTrustEmail(true); identityProviderModel.setStoreToken(true); identityProviderModel.setAuthenticateByDefault(true); @@ -97,7 +96,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes assertEquals("value-added", identityProviderModel.getConfig().get("config-added")); assertFalse(identityProviderModel.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_OFF, identityProviderModel.getUpdateProfileFirstLoginMode()); assertTrue(identityProviderModel.isTrustEmail()); assertTrue(identityProviderModel.isStoreToken()); assertTrue(identityProviderModel.isAuthenticateByDefault()); @@ -105,7 +103,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes identityProviderModel.getConfig().remove("config-added"); identityProviderModel.setEnabled(true); - identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_MISSING); identityProviderModel.setTrustEmail(false); identityProviderModel.setAuthenticateByDefault(false); @@ -118,7 +115,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes assertFalse(identityProviderModel.getConfig().containsKey("config-added")); assertTrue(identityProviderModel.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_MISSING, identityProviderModel.getUpdateProfileFirstLoginMode()); assertFalse(identityProviderModel.isTrustEmail()); assertFalse(identityProviderModel.isAuthenticateByDefault()); this.realmManager.removeRealm(realm); @@ -167,7 +163,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes assertEquals("model-google", config.getAlias()); assertEquals(GoogleIdentityProviderFactory.PROVIDER_ID, config.getProviderId()); assertEquals(true, config.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_ON, config.getUpdateProfileFirstLoginMode()); assertEquals(true, config.isTrustEmail()); assertEquals(false, config.isAuthenticateByDefault()); assertEquals(true, config.isStoreToken()); @@ -186,7 +181,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes assertEquals("model-saml-signed-idp", config.getAlias()); assertEquals(SAMLIdentityProviderFactory.PROVIDER_ID, config.getProviderId()); assertEquals(true, config.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_ON, config.getUpdateProfileFirstLoginMode()); assertEquals(false, config.isAuthenticateByDefault()); assertEquals(false, config.isTrustEmail()); assertEquals(false, config.isStoreToken()); @@ -207,7 +201,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes assertEquals("model-oidc-idp", config.getAlias()); assertEquals(OIDCIdentityProviderFactory.PROVIDER_ID, config.getProviderId()); assertEquals(false, config.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode()); assertEquals(false, config.isTrustEmail()); assertEquals(false, config.isAuthenticateByDefault()); assertEquals(false, config.isStoreToken()); @@ -222,7 +215,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes assertEquals("model-facebook", config.getAlias()); assertEquals(FacebookIdentityProviderFactory.PROVIDER_ID, config.getProviderId()); assertEquals(true, config.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode()); assertEquals(false, config.isTrustEmail()); assertEquals(false, config.isAuthenticateByDefault()); assertEquals(false, config.isStoreToken()); @@ -241,7 +233,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes assertEquals("model-github", config.getAlias()); assertEquals(GitHubIdentityProviderFactory.PROVIDER_ID, config.getProviderId()); assertEquals(true, config.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_ON, config.getUpdateProfileFirstLoginMode()); assertEquals(false, config.isTrustEmail()); assertEquals(false, config.isAuthenticateByDefault()); assertEquals(false, config.isStoreToken()); @@ -258,17 +249,16 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes OAuth2IdentityProviderConfig config = liIdentityProvider.getConfig(); assertEquals("model-linkedin", config.getAlias()); - assertEquals(LinkedInIdentityProviderFactory.PROVIDER_ID, config.getProviderId()); - assertEquals(true, config.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_MISSING, config.getUpdateProfileFirstLoginMode()); + assertEquals(LinkedInIdentityProviderFactory.PROVIDER_ID, config.getProviderId()); + assertEquals(true, config.isEnabled()); assertEquals(false, config.isTrustEmail()); - assertEquals(false, config.isAuthenticateByDefault()); - assertEquals(false, config.isStoreToken()); - assertEquals("clientId", config.getClientId()); - assertEquals("clientSecret", config.getClientSecret()); - assertEquals(LinkedInIdentityProvider.AUTH_URL, config.getAuthorizationUrl()); - assertEquals(LinkedInIdentityProvider.TOKEN_URL, config.getTokenUrl()); - assertEquals(LinkedInIdentityProvider.PROFILE_URL, config.getUserInfoUrl()); + assertEquals(false, config.isAuthenticateByDefault()); + assertEquals(false, config.isStoreToken()); + assertEquals("clientId", config.getClientId()); + assertEquals("clientSecret", config.getClientSecret()); + assertEquals(LinkedInIdentityProvider.AUTH_URL, config.getAuthorizationUrl()); + assertEquals(LinkedInIdentityProvider.TOKEN_URL, config.getTokenUrl()); + assertEquals(LinkedInIdentityProvider.PROFILE_URL, config.getUserInfoUrl()); } private void assertStackoverflowIdentityProviderConfig(IdentityProviderModel identityProvider) { @@ -278,7 +268,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes assertEquals("model-stackoverflow", config.getAlias()); assertEquals(StackoverflowIdentityProviderFactory.PROVIDER_ID, config.getProviderId()); assertEquals(true, config.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode()); assertEquals(false, config.isTrustEmail()); assertEquals(false, config.isAuthenticateByDefault()); assertEquals(false, config.isStoreToken()); @@ -297,7 +286,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes assertEquals("model-twitter", config.getAlias()); assertEquals(TwitterIdentityProviderFactory.PROVIDER_ID, config.getProviderId()); assertEquals(true, config.isEnabled()); - assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode()); assertEquals(false, config.isTrustEmail()); assertEquals(false, config.isAuthenticateByDefault()); assertEquals(true, config.isStoreToken()); diff --git a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json index a4f204d146..0b8b79e947 100755 --- a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json +++ b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json @@ -17,7 +17,6 @@ "alias" : "model-google", "providerId" : "google", "enabled": true, - "updateProfileFirstLogin" : "true", "trustEmail" : "true", "storeToken": "true", "config": { @@ -29,7 +28,6 @@ "alias" : "model-facebook", "providerId" : "facebook", "enabled": true, - "updateProfileFirstLogin" : "false", "firstBrokerLoginFlowAlias" : "browser", "config": { "authorizationUrl": "authorizationUrl", @@ -43,7 +41,6 @@ "alias" : "model-github", "providerId" : "github", "enabled": true, - "updateProfileFirstLoginMode" : "on", "storeToken": "false", "config": { "authorizationUrl": "authorizationUrl", @@ -57,7 +54,6 @@ "alias" : "model-twitter", "providerId" : "twitter", "enabled": true, - "updateProfileFirstLoginMode" : "off", "storeToken": true, "config": { "authorizationUrl": "authorizationUrl", @@ -71,7 +67,6 @@ "alias" : "model-linkedin", "providerId" : "linkedin", "enabled": true, - "updateProfileFirstLoginMode" : "missing", "storeToken": false, "config": { "authorizationUrl": "authorizationUrl", @@ -85,7 +80,6 @@ "alias" : "model-stackoverflow", "providerId" : "stackoverflow", "enabled": true, - "updateProfileFirstLoginMode" : "off", "storeToken": false, "config": { "key": "keyValue", @@ -100,7 +94,6 @@ "alias" : "model-saml-signed-idp", "providerId" : "saml", "enabled": true, - "updateProfileFirstLoginMode" : "on", "config": { "singleSignOnServiceUrl": "http://localhost:8082/auth/realms/realm-with-saml-identity-provider/protocol/saml", "nameIDPolicyFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", @@ -116,7 +109,6 @@ "alias" : "kc-saml-signed-idp", "providerId" : "saml", "enabled": true, - "updateProfileFirstLoginMode" : "on", "addReadTokenRoleOnCreate": true, "config": { "singleSignOnServiceUrl": "http://localhost:8082/auth/realms/realm-with-saml-signed-idp/protocol/saml", @@ -135,7 +127,6 @@ "alias" : "kc-saml-idp-basic", "providerId" : "saml", "enabled": true, - "updateProfileFirstLoginMode" : "on", "trustEmail" : false, "addReadTokenRoleOnCreate": true, "config": { @@ -151,7 +142,6 @@ "alias" : "model-oidc-idp", "providerId" : "oidc", "enabled": false, - "updateProfileFirstLoginMode" : "off", "authenticateByDefault" : "false", "config": { "clientId": "clientId", @@ -167,7 +157,6 @@ "alias" : "kc-oidc-idp", "providerId" : "keycloak-oidc", "enabled": true, - "updateProfileFirstLoginMode" : "off", "storeToken" : true, "addReadTokenRoleOnCreate": true, "config": {