From bb77ab4a812c87b5faa25383a637c034321da0b1 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 27 Jan 2017 17:37:08 -0500 Subject: [PATCH] account link tests --- ...ssThroughFederatedUserStorageProvider.java | 170 ++++++++++++++++ ...ghFederatedUserStorageProviderFactory.java | 40 ++++ ...eycloak.storage.UserStorageProviderFactory | 3 +- .../testsuite/broker/AccountLinkTest.java | 186 ++++++++++++++++++ .../testsuite/broker/BrokerTestTools.java | 51 +++++ 5 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProviderFactory.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AccountLinkTest.java diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java new file mode 100644 index 0000000000..2e993855e9 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java @@ -0,0 +1,170 @@ +/* + * 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.federation; + +import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialInputUpdater; +import org.keycloak.credential.CredentialInputValidator; +import org.keycloak.credential.CredentialModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; +import org.keycloak.storage.user.UserLookupProvider; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Provides one user where everything is stored in user federated storage + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class PassThroughFederatedUserStorageProvider implements + UserStorageProvider, + UserLookupProvider, + CredentialInputValidator, + CredentialInputUpdater +{ + + public static final Set CREDENTIAL_TYPES = Collections.singleton(UserCredentialModel.PASSWORD); + public static final String PASSTHROUGH_USERNAME = "passthrough"; + public static final String INITIAL_PASSWORD = "secret"; + private KeycloakSession session; + private ComponentModel component; + + public PassThroughFederatedUserStorageProvider(KeycloakSession session, ComponentModel component) { + this.session = session; + this.component = component; + } + + public Set getSupportedCredentialTypes() { + return CREDENTIAL_TYPES; + } + + @Override + public boolean supportsCredentialType(String credentialType) { + return getSupportedCredentialTypes().contains(credentialType); + } + + @Override + public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { + if (!CredentialModel.PASSWORD.equals(credentialType)) return false; + return true; + } + + @Override + public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) { + UserCredentialModel password = (UserCredentialModel)input; + if (password.getType().equals(UserCredentialModel.PASSWORD)) { + if (INITIAL_PASSWORD.equals(password.getValue())) { + return true; + } + List existing = session.userFederatedStorage().getStoredCredentialsByType(realm, user.getId(), "CLEAR_TEXT_PASSWORD"); + if (existing.isEmpty()) return false; + return existing.get(0).getConfig().getFirst("VALUE").equals(password.getValue()); + } + return false; + } + + @Override + public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { + // testing federated credential attributes + UserCredentialModel password = (UserCredentialModel)input; + if (password.getType().equals(UserCredentialModel.PASSWORD)) { + List existing = session.userFederatedStorage().getStoredCredentialsByType(realm, user.getId(), "CLEAR_TEXT_PASSWORD"); + if (existing.isEmpty()) { + CredentialModel model = new CredentialModel(); + model.setType("CLEAR_TEXT_PASSWORD"); + model.getConfig().putSingle("VALUE", password.getValue()); + session.userFederatedStorage().createCredential(realm, user.getId(), model); + } else { + CredentialModel model = existing.get(0); + model.setType("CLEAR_TEXT_PASSWORD"); + model.getConfig().putSingle("VALUE", password.getValue()); + session.userFederatedStorage().updateCredential(realm, user.getId(), model); + + } + return true; + } + return false; + } + + @Override + public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) { + List existing = session.userFederatedStorage().getStoredCredentialsByType(realm, user.getId(), "CLEAR_TEXT_PASSWORD"); + for (CredentialModel model : existing) { + session.userFederatedStorage().removeStoredCredential(realm, user.getId(), model.getId()); + } + } + + @Override + public Set getDisableableCredentialTypes(RealmModel realm, UserModel user) { + return CREDENTIAL_TYPES; + } + + @Override + public void close() { + + } + + @Override + public UserModel getUserById(String id, RealmModel realm) { + if (!StorageId.externalId(id).equals(PASSTHROUGH_USERNAME)) return null; + return getUserModel(realm); + } + + @Override + public UserModel getUserByUsername(String username, RealmModel realm) { + if (!PASSTHROUGH_USERNAME.equals(username)) return null; + + return getUserModel(realm); + } + + @Override + public UserModel getUserByEmail(String email, RealmModel realm) { + List list = session.userFederatedStorage().getUsersByUserAttribute(realm, AbstractUserAdapterFederatedStorage.EMAIL_ATTRIBUTE, email); + for (String user : list) { + StorageId storageId = new StorageId(user); + if (!storageId.getExternalId().equals(PASSTHROUGH_USERNAME)) continue; + if (!storageId.getProviderId().equals(component.getId())) continue; + return getUserModel(realm); + + } + return null; + } + + private UserModel getUserModel(final RealmModel realm) { + return new AbstractUserAdapterFederatedStorage(session, realm, component) { + @Override + public String getUsername() { + return PASSTHROUGH_USERNAME; + } + + @Override + public void setUsername(String username) { + + } + }; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProviderFactory.java new file mode 100644 index 0000000000..1b3cb55950 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProviderFactory.java @@ -0,0 +1,40 @@ +/* + * 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.federation; + +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.storage.UserStorageProviderFactory; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class PassThroughFederatedUserStorageProviderFactory implements UserStorageProviderFactory { + + public static final String PROVIDER_ID = "pass-through-federated"; + + @Override + public PassThroughFederatedUserStorageProvider create(KeycloakSession session, ComponentModel model) { + return new PassThroughFederatedUserStorageProvider(session, model); + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory index a97dd1ebb6..a9ae823e92 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -1 +1,2 @@ -org.keycloak.testsuite.federation.DummyUserFederationProviderFactory \ No newline at end of file +org.keycloak.testsuite.federation.DummyUserFederationProviderFactory +org.keycloak.testsuite.federation.PassThroughFederatedUserStorageProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AccountLinkTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AccountLinkTest.java new file mode 100644 index 0000000000..7e796cd8a9 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AccountLinkTest.java @@ -0,0 +1,186 @@ +/* + * 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.broker; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.federation.PassThroughFederatedUserStorageProvider; +import org.keycloak.testsuite.federation.PassThroughFederatedUserStorageProviderFactory; +import org.keycloak.testsuite.pages.AccountFederatedIdentityPage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.UpdateAccountInformationPage; + +import java.util.List; + +import static org.junit.Assert.assertTrue; +import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient; +import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient; +import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_PROV_NAME; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class AccountLinkTest extends AbstractKeycloakTest { + public static final String CHILD_IDP = "child"; + public static final String PARENT_IDP = "parent-idp"; + public static final String PARENT_USERNAME = "parent"; + + @Page + protected AccountFederatedIdentityPage accountFederatedIdentityPage; + + @Page + protected UpdateAccountInformationPage profilePage; + + @Page + protected LoginPage loginPage; + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = new RealmRepresentation(); + realm.setRealm(CHILD_IDP); + realm.setEnabled(true); + testRealms.add(realm); + + realm = new RealmRepresentation(); + realm.setRealm(PARENT_IDP); + realm.setEnabled(true); + + testRealms.add(realm); + + } + + @Before + public void addIdpUser() { + RealmResource realm = adminClient.realms().realm(PARENT_IDP); + UserRepresentation user = new UserRepresentation(); + user.setUsername(PARENT_USERNAME); + user.setEnabled(true); + String userId = createUserAndResetPasswordWithAdminClient(realm, user, "password"); + + } + + @Before + public void addChildUser() { + RealmResource realm = adminClient.realms().realm(CHILD_IDP); + UserRepresentation user = new UserRepresentation(); + user.setUsername("child"); + user.setEnabled(true); + String userId = createUserAndResetPasswordWithAdminClient(realm, user, "password"); + + } + + @Before + public void setupUserStorageProvider() { + ComponentRepresentation provider = new ComponentRepresentation(); + provider.setName("passthrough"); + provider.setProviderId(PassThroughFederatedUserStorageProviderFactory.PROVIDER_ID); + provider.setProviderType(UserStorageProvider.class.getName()); + provider.setConfig(new MultivaluedHashMap<>()); + provider.getConfig().putSingle("priority", Integer.toString(1)); + + RealmResource realm = adminClient.realms().realm(CHILD_IDP); + realm.components().add(provider); + + + + + } + + @Before + public void createBroker() { + createParentChild(); + } + + public void createParentChild() { + BrokerTestTools.createKcOidcBroker(adminClient, CHILD_IDP, PARENT_IDP, suiteContext); + } + + @Test + public void testAccountLink() { + String childUsername = "child"; + String childPassword = "password"; + String childIdp = CHILD_IDP; + + testAccountLink(childUsername, childPassword, childIdp); + + } + + @Test + public void testAccountLinkWithUserStorageProvider() { + String childUsername = PassThroughFederatedUserStorageProvider.PASSTHROUGH_USERNAME; + String childPassword = PassThroughFederatedUserStorageProvider.INITIAL_PASSWORD; + String childIdp = CHILD_IDP; + + testAccountLink(childUsername, childPassword, childIdp); + + } + + protected void testAccountLink(String childUsername, String childPassword, String childIdp) { + accountFederatedIdentityPage.realm(childIdp); + accountFederatedIdentityPage.open(); + loginPage.isCurrent(); + loginPage.login(childUsername, childPassword); + assertTrue(accountFederatedIdentityPage.isCurrent()); + + accountFederatedIdentityPage.clickAddProvider(PARENT_IDP); + + this.loginPage.isCurrent(); + loginPage.login(PARENT_USERNAME, "password"); + + // Assert identity linked in account management + assertTrue(accountFederatedIdentityPage.isCurrent()); + assertTrue(driver.getPageSource().contains("id=\"remove-" + PARENT_IDP + "\"")); + + // Logout from account management + accountFederatedIdentityPage.logout(); + + // Assert I am logged immediately to account management due to previously linked "test-user" identity + loginPage.isCurrent(); + loginPage.clickSocial(PARENT_IDP); + loginPage.login(PARENT_USERNAME, "password"); + System.out.println(driver.getCurrentUrl()); + System.out.println("--------------------------------"); + System.out.println(driver.getPageSource()); + assertTrue(accountFederatedIdentityPage.isCurrent()); + assertTrue(driver.getPageSource().contains("id=\"remove-" + PARENT_IDP + "\"")); + + // Unlink my "test-user" + accountFederatedIdentityPage.clickRemoveProvider(PARENT_IDP); + assertTrue(driver.getPageSource().contains("id=\"add-" + PARENT_IDP + "\"")); + + + // Logout from account management + accountFederatedIdentityPage.logout(); + + this.loginPage.clickSocial(PARENT_IDP); + this.loginPage.login(PARENT_USERNAME, "password"); + this.profilePage.assertCurrent(); + } + + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/BrokerTestTools.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/BrokerTestTools.java index f021a366b9..c5b7b315af 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/BrokerTestTools.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/BrokerTestTools.java @@ -1,17 +1,29 @@ package org.keycloak.testsuite.broker; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.testsuite.arquillian.SuiteContext; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.util.Collections; import java.util.List; +import java.util.Map; + import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.WebDriverWait; +import static org.keycloak.testsuite.broker.BrokerTestConstants.CLIENT_ID; +import static org.keycloak.testsuite.broker.BrokerTestConstants.CLIENT_SECRET; +import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS; +import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID; +import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_CONS_NAME; +import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_PROV_NAME; + /** * * @author hmlnarik @@ -62,4 +74,43 @@ public class BrokerTestTools { return result; } + + /** + * Expects a child idp and parent idp running on same Keycloak instance. Links the two with non-signature checks. + * + * @param adminClient + * @param childRealm + * @param idpRealm + * @param suiteContext + */ + public static void createKcOidcBroker(Keycloak adminClient, String childRealm, String idpRealm, SuiteContext suiteContext) { + IdentityProviderRepresentation idp = createIdentityProvider(idpRealm, IDP_OIDC_PROVIDER_ID); + Map config = idp.getConfig(); + + config.put("clientId", childRealm); + config.put("clientSecret", childRealm); + config.put("prompt", "login"); + config.put("authorizationUrl", getAuthRoot(suiteContext) + "/auth/realms/" + idpRealm + "/protocol/openid-connect/auth"); + config.put("tokenUrl", getAuthRoot(suiteContext) + "/auth/realms/" + idpRealm + "/protocol/openid-connect/token"); + config.put("logoutUrl", getAuthRoot(suiteContext) + "/auth/realms/" + idpRealm + "/protocol/openid-connect/logout"); + config.put("userInfoUrl", getAuthRoot(suiteContext) + "/auth/realms/" + idpRealm + "/protocol/openid-connect/userinfo"); + config.put("backchannelSupported", "true"); + adminClient.realm(childRealm).identityProviders().create(idp); + + ClientRepresentation client = new ClientRepresentation(); + client.setClientId(childRealm); + client.setName(childRealm); + client.setSecret(childRealm); + client.setEnabled(true); + + client.setRedirectUris(Collections.singletonList(getAuthRoot(suiteContext) + + "/auth/realms/" + childRealm + "/broker/" + idpRealm + "/endpoint/*")); + + client.setAdminUrl(getAuthRoot(suiteContext) + + "/auth/realms/" + childRealm + "/broker/" + idpRealm + "/endpoint"); + adminClient.realm(idpRealm).clients().create(client); + + + + } }