diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java index 87b935a95a..f682a3f08c 100755 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java @@ -27,6 +27,7 @@ import java.net.URI; import java.util.Comparator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -63,10 +64,13 @@ public class IdentityProviderBean { private void addIdentityProvider(Set orderedSet, RealmModel realm, URI baseURI, IdentityProviderModel identityProvider) { String loginUrl = Urls.identityProviderAuthnRequest(baseURI, identityProvider.getAlias(), realm.getName()).toString(); String displayName = KeycloakModelUtils.getIdentityProviderDisplayName(session, identityProvider); - - orderedSet.add(new IdentityProvider(identityProvider.getAlias(), - displayName, identityProvider.getProviderId(), loginUrl, - identityProvider.getConfig() != null ? identityProvider.getConfig().get("guiOrder") : null)); + Map config = identityProvider.getConfig(); + boolean hideOnLoginPage = config != null && Boolean.parseBoolean(config.get("hideOnLoginPage")); + if (!hideOnLoginPage) { + orderedSet.add(new IdentityProvider(identityProvider.getAlias(), + displayName, identityProvider.getProviderId(), loginUrl, + config != null ? config.get("guiOrder") : null)); + } } public List getProviders() { diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java index ebd6773ac4..9ca2372dbb 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java @@ -50,13 +50,23 @@ public class AccountFederatedIdentityPage extends AbstractAccountPage { public boolean isCurrent() { return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Federated Identities"); } - - public void clickAddProvider(String providerId) { - driver.findElement(By.id("add-" + providerId)).click(); + + public WebElement findAddProviderButton(String alias) { + return driver.findElement(By.id("add-" + alias)); + } + + public WebElement findRemoveProviderButton(String alias) { + return driver.findElement(By.id("remove-" + alias)); } - public void clickRemoveProvider(String providerId) { - driver.findElement(By.id("remove-" + providerId)).click(); + public void clickAddProvider(String alias) { + WebElement addButton = findAddProviderButton(alias); + addButton.click(); + } + + public void clickRemoveProvider(String alias) { + WebElement addButton = findRemoveProviderButton(alias); + addButton.click(); } public String getError() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java index 4c408ba187..3f76556191 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -40,6 +40,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.drone.Different; import org.keycloak.testsuite.pages.AccountApplicationsPage; +import org.keycloak.testsuite.pages.AccountFederatedIdentityPage; import org.keycloak.testsuite.pages.AccountLogPage; import org.keycloak.testsuite.pages.AccountPasswordPage; import org.keycloak.testsuite.pages.AccountSessionsPage; @@ -95,6 +96,12 @@ public class AccountTest extends AbstractTestRealmKeycloakTest { .alias("myoidc") .displayName("MyOIDC") .build()); + testRealm.addIdentityProvider(IdentityProviderBuilder.create() + .providerId("oidc") + .alias("myhiddenoidc") + .displayName("MyHiddenOIDC") + .hideOnLoginPage() + .build()); RealmBuilder.edit(testRealm) .user(user2); @@ -138,6 +145,9 @@ public class AccountTest extends AbstractTestRealmKeycloakTest { @Page protected AccountApplicationsPage applicationsPage; + + @Page + protected AccountFederatedIdentityPage federatedIdentityPage; @Page protected ErrorPage errorPage; @@ -918,7 +928,13 @@ public class AccountTest extends AbstractTestRealmKeycloakTest { Assert.assertEquals("GitHub", loginPage.findSocialButton("github").getText()); Assert.assertEquals("mysaml", loginPage.findSocialButton("mysaml").getText()); Assert.assertEquals("MyOIDC", loginPage.findSocialButton("myoidc").getText()); - + } + + @Test + public void testIdentityProviderHiddenOnLoginPageIsVisbleInAccount(){ + federatedIdentityPage.open(); + loginPage.login("test-user@localhost", "password"); + Assert.assertNotNull(federatedIdentityPage.findAddProviderButton("myhiddenoidc")); } @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/HiddenProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/HiddenProviderTest.java new file mode 100644 index 0000000000..eae92a7ceb --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/HiddenProviderTest.java @@ -0,0 +1,65 @@ +/* + * 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.testsuite.forms; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Assert; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.util.IdentityProviderBuilder; + +public class HiddenProviderTest extends AbstractTestRealmKeycloakTest { + + @Page + protected LoginPage loginPage; + + @Override + protected RealmResource testRealm() { + return adminClient.realm("realm-with-broker"); + } + + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + testRealm.addIdentityProvider(IdentityProviderBuilder.create() + .providerId("oidc") + .alias("visible-oidc") + .displayName("VisibleOIDC") + .build()); + testRealm.addIdentityProvider(IdentityProviderBuilder.create() + .providerId("oidc") + .alias("hidden-oidc") + .displayName("HiddenOIDC") + .hideOnLoginPage() + .build()); + } + + @Test + public void testVisibleProviderButton() { + loginPage.open(); + Assert.assertNotNull(loginPage.findSocialButton("visible-oidc")); + } + + @Test(expected=org.openqa.selenium.NoSuchElementException.class) + public void testHiddenProviderButton() { + loginPage.open(); + Assert.assertNull(loginPage.findSocialButton("hidden-oidc")); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/IdentityProviderBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/IdentityProviderBuilder.java index 7cb1446df6..7fccde75c3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/IdentityProviderBuilder.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/IdentityProviderBuilder.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.util; +import java.util.HashMap; import org.keycloak.representations.idm.IdentityProviderRepresentation; /** @@ -48,6 +49,14 @@ public class IdentityProviderBuilder { rep.setDisplayName(displayName); return this; } + + public IdentityProviderBuilder hideOnLoginPage() { + if (rep.getConfig() == null) { + rep.setConfig(new HashMap<>()); + } + rep.getConfig().put("hideOnLoginPage", "true"); + return this; + } public IdentityProviderRepresentation build() { return rep; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java index 36c291f02b..05d1afa116 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java @@ -87,6 +87,13 @@ public class IdentityProviderHintTest { assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); assertTrue(this.driver.getPageSource().contains("idToken")); } + + @Test + public void testSuccessfulRedirectToProviderHiddenOnLoginPage() { + this.driver.navigate().to("http://localhost:8081/test-app?kc_idp_hint=kc-oidc-idp-hidden"); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); + } @Test public void testInvalidIdentityProviderHint() { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java index 4b3ecb4aa4..2fe160ecd7 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java @@ -143,13 +143,13 @@ public class LoginPage extends AbstractPage { registerLink.click(); } - public void clickSocial(String providerId) { - WebElement socialButton = findSocialButton(providerId); + public void clickSocial(String alias) { + WebElement socialButton = findSocialButton(alias); socialButton.click(); } - public WebElement findSocialButton(String providerId) { - String id = "zocial-" + providerId; + public WebElement findSocialButton(String alias) { + String id = "zocial-" + alias; return this.driver.findElement(By.id(id)); } 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 ab707149f2..1bfc295905 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 @@ -205,6 +205,24 @@ "defaultScope": "email profile", "backchannelSupported": "true" } + }, + { + "alias" : "kc-oidc-idp-hidden", + "providerId" : "keycloak-oidc", + "enabled": true, + "storeToken" : true, + "addReadTokenRoleOnCreate": true, + "config": { + "clientId": "broker-app", + "clientSecret": "secret", + "authorizationUrl": "http://localhost:8082/auth/realms/realm-with-oidc-idp-hidden/protocol/openid-connect/auth", + "tokenUrl": "http://localhost:8082/auth/realms/realm-with-oidc-idp-hidden/protocol/openid-connect/token", + "userInfoUrl": "http://localhost:8082/auth/realms/realm-with-oidc-idp-hidden/protocol/openid-connect/userinfo", + "logoutUrl": "http://localhost:8082/auth/realms/realm-with-oidc-idp-hidden/protocol/openid-connect/logout", + "defaultScope": "email profile", + "backchannelSupported": "true", + "hideOnLoginPage": true + } } ], "identityProviderMappers": [ diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 76c6d5078e..2ff5f9679e 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -2,6 +2,7 @@ consoleTitle=Keycloak Admin Console # Common messages enabled=Enabled +hidden=Hidden name=Name displayName=Display name displayNameHtml=HTML Display name @@ -466,6 +467,8 @@ off=Off update-profile-on-first-login.tooltip=Define conditions under which a user has to update their profile during first-time login. trust-email=Trust Email trust-email.tooltip=If enabled then email provided by this provider is not verified even if verification is enabled for the realm. +hide-on-login-page=Hide on Login Page +hide-on-login-page.tooltip=If hidden, then login with this provider is possible only if requested explicitly, e.g. using the 'kc_idp_hint' parameter. gui-order.tooltip=Number defining order of the provider in GUI (eg. on Login page). first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that there is not yet existing Keycloak account linked with the authenticated identity provider account. post-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after each login with this identity provider. Useful if you want additional verification of each user authenticated with this identity provider (for example OTP). Leave this empty if you don't want any additional authenticators to be triggered after login with this identity provider. Also note, that authenticator implementations must assume that user is already set in ClientSession as identity provider already set it. diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index b4069ea1d4..6a04d37288 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -64,6 +64,13 @@ {{:: 'trust-email.tooltip' | translate}} +
+ +
+ +
+ {{:: 'hide-on-login-page.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html index 3aad92beb0..83010b62e6 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html @@ -61,6 +61,13 @@
{{:: 'trust-email.tooltip' | translate}}
+
+ +
+ +
+ {{:: 'hide-on-login-page.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html index d5c0d85221..8e200e7028 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html @@ -78,6 +78,13 @@
{{:: 'trust-email.tooltip' | translate}}
+
+ +
+ +
+ {{:: 'hide-on-login-page.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html index ade7b83d43..22ac869164 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html @@ -33,7 +33,7 @@ {{:: 'table-of-identity-providers' | translate}} - +