From 783545572aed510f9f79e50ac38d5f2d08c3a61e Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Tue, 8 Oct 2019 23:00:27 +0200 Subject: [PATCH] KEYCLOAK-11684 Add support to display passwords in password fields Add UI tests for KEYCLOAK-11684 Co-authored-by: stianst Co-authored-by: vmuzikar --- .../models/utils/StripSecretsUtils.java | 32 ++++- .../console/page/fragment/KcPassword.java | 59 ++++++++ .../testsuite/admin/ComponentsTest.java | 18 ++- .../testsuite/admin/IdentityProviderTest.java | 11 ++ .../page/idp/CreateIdentityProvider.java | 52 +++++++ .../console/page/idp/IdentityProvider.java | 53 ++++++++ .../page/idp/IdentityProviderForm.java | 48 +++++++ .../console/page/idp/IdentityProviders.java | 69 ++++++++++ .../console/idp/IdentityProviderTest.java | 127 ++++++++++++++---- .../theme/base/admin/resources/js/app.js | 47 ++++++- 10 files changed, 480 insertions(+), 36 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/KcPassword.java create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/CreateIdentityProvider.java create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProvider.java create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProviderForm.java create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProviders.java diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java index 5bdd018172..80e4089ef0 100644 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java @@ -23,7 +23,6 @@ import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ComponentExportRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; -import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -32,12 +31,25 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * @author Stian Thorgersen */ public class StripSecretsUtils { + private static final Pattern VAULT_VALUE = Pattern.compile("^\\$\\{vault\\.(.+?)}$"); + + private static String maskNonVaultValue(String value) { + return value == null + ? null + : (VAULT_VALUE.matcher(value).matches() + ? value + : ComponentRepresentation.SECRET_VALUE + ); + } + public static ComponentRepresentation strip(KeycloakSession session, ComponentRepresentation rep) { Map configProperties = ComponentUtil.getComponentConfigProperties(session, rep); if (rep.getConfig() == null) { @@ -50,7 +62,11 @@ public class StripSecretsUtils { ProviderConfigProperty configProperty = configProperties.get(next.getKey()); if (configProperty != null) { if (configProperty.isSecret()) { - next.setValue(Collections.singletonList(ComponentRepresentation.SECRET_VALUE)); + if (next.getValue() == null || next.getValue().isEmpty()) { + next.setValue(Collections.singletonList(ComponentRepresentation.SECRET_VALUE)); + } else { + next.setValue(next.getValue().stream().map(StripSecretsUtils::maskNonVaultValue).collect(Collectors.toList())); + } } } else { itr.remove(); @@ -61,14 +77,14 @@ public class StripSecretsUtils { public static RealmRepresentation strip(RealmRepresentation rep) { if (rep.getSmtpServer() != null && rep.getSmtpServer().containsKey("password")) { - rep.getSmtpServer().put("password", ComponentRepresentation.SECRET_VALUE); + rep.getSmtpServer().put("password", maskNonVaultValue(rep.getSmtpServer().get("password"))); } return rep; } public static IdentityProviderRepresentation strip(IdentityProviderRepresentation rep) { if (rep.getConfig() != null && rep.getConfig().containsKey("clientSecret")) { - rep.getConfig().put("clientSecret", ComponentRepresentation.SECRET_VALUE); + rep.getConfig().put("clientSecret", maskNonVaultValue(rep.getConfig().get("clientSecret"))); } return rep; } @@ -122,7 +138,7 @@ public class StripSecretsUtils { public static ClientRepresentation strip(ClientRepresentation rep) { if (rep.getSecret() != null) { - rep.setSecret(ComponentRepresentation.SECRET_VALUE); + rep.setSecret(maskNonVaultValue(rep.getSecret())); } return rep; } @@ -139,7 +155,11 @@ public class StripSecretsUtils { ProviderConfigProperty configProperty = configProperties.get(next.getKey()); if (configProperty != null) { if (configProperty.isSecret()) { - next.setValue(Collections.singletonList(ComponentRepresentation.SECRET_VALUE)); + if (next.getValue() == null || next.getValue().isEmpty()) { + next.setValue(Collections.singletonList(ComponentRepresentation.SECRET_VALUE)); + } else { + next.setValue(next.getValue().stream().map(StripSecretsUtils::maskNonVaultValue).collect(Collectors.toList())); + } } } else { itr.remove(); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/KcPassword.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/KcPassword.java new file mode 100644 index 0000000000..a58ae9592d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/KcPassword.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 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.console.page.fragment; + +import org.jboss.arquillian.graphene.fragment.Root; +import org.openqa.selenium.ElementNotInteractableException; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +import static org.keycloak.testsuite.util.UIUtils.setTextInputValue; + +/** + * @author Vaclav Muzikar + */ +public class KcPassword { + @Root + private WebElement inputField; + + @FindBy(xpath = "../span[contains(@class,'input-group-addon') and ./span[contains(@class,'fa-eye')]]") + private WebElement eyeButton; + + public void setValue(final String value) { + setTextInputValue(inputField, value); + } + + public boolean isMasked() { + return inputField.getAttribute("class").contains("password-conceal"); + } + + public boolean isEyeButtonDisabled() { + return eyeButton.getAttribute("class").contains("disabled"); + } + + public void clickEyeButton() { + if (isEyeButtonDisabled()) { + throw new ElementNotInteractableException("The eye button is disabled and cannot be clicked"); + } + eyeButton.click(); + } + + public WebElement getElement() { + return inputField; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java index 78dfe91de9..1e485d3681 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java @@ -37,7 +37,10 @@ import java.util.concurrent.*; import java.util.function.BiConsumer; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.hamcrest.Matchers; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; /** * @author Stian Thorgersen @@ -255,8 +258,8 @@ public class ComponentsTest extends AbstractAdminTest { // Check secret not leaked in admin events event = testingClient.testing().pollAdminEvent(); - assertFalse(event.getRepresentation().contains("some secret value!!")); - assertTrue(event.getRepresentation().contains(ComponentRepresentation.SECRET_VALUE)); + assertThat(event.getRepresentation(), not(containsString("some secret value!!"))); + assertThat(event.getRepresentation(), containsString(ComponentRepresentation.SECRET_VALUE)); // Check secret value is not set to '*********' details = testingClient.testing(REALM_NAME).getTestComponentDetails(); @@ -271,6 +274,17 @@ public class ComponentsTest extends AbstractAdminTest { ComponentRepresentation returned3 = components.query().stream().filter(c -> c.getId().equals(returned2.getId())).findFirst().get(); assertEquals(ComponentRepresentation.SECRET_VALUE, returned3.getConfig().getFirst("secret")); + + + returned2.getConfig().putSingle("secret", "${vault.value}"); + components.component(id).update(returned2); + + // Check secret value is updated + details = testingClient.testing(REALM_NAME).getTestComponentDetails(); + assertThat(details.get("mycomponent").getConfig().get("secret"), contains("${vault.value}")); + + ComponentRepresentation returned4 = components.query().stream().filter(c -> c.getId().equals(returned2.getId())).findFirst().get(); + assertThat(returned4.getConfig().get("secret"), contains("${vault.value}")); } @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java index 9c62663299..3266ead71a 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java @@ -61,11 +61,13 @@ import java.util.Map; import java.util.Set; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -245,6 +247,15 @@ public class IdentityProviderTest extends AbstractAdminTest { assertEquals("changedClientId", representation.getConfig().get("clientId")); assertEquals("some secret value", testingClient.testing("admin-client-test").getIdentityProviderConfig("changed-alias").get("clientSecret")); + + representation.getConfig().put("clientSecret", "${vault.key}"); + identityProviderResource.update(representation); + event = assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.identityProviderPath(representation.getInternalId()), representation, ResourceType.IDENTITY_PROVIDER); + assertThat(event.getRepresentation(), containsString("${vault.key}")); + assertThat(event.getRepresentation(), not(containsString(ComponentRepresentation.SECRET_VALUE))); + + assertThat(identityProviderResource.toRepresentation().getConfig(), hasEntry("clientSecret", "${vault.key}")); + assertEquals("${vault.key}", testingClient.testing("admin-client-test").getIdentityProviderConfig("changed-alias").get("clientSecret")); } @Test diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/CreateIdentityProvider.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/CreateIdentityProvider.java new file mode 100644 index 0000000000..f24bec4ce4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/CreateIdentityProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 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.console.page.idp; + +import org.jboss.arquillian.graphene.page.Page; +import org.keycloak.testsuite.console.page.AdminConsoleCreate; + +/** + * @author Vaclav Muzikar + */ +public class CreateIdentityProvider extends AdminConsoleCreate { + public static final String PROVIDER_ID = "provider-id"; + + @Page + private IdentityProviderForm form; + + public CreateIdentityProvider() { + setEntity("identity-provider"); + } + + @Override + public String getUriFragment() { + return super.getUriFragment() + "/{" + PROVIDER_ID + "}"; + } + + public void setProviderId(String id) { + setUriParameter(PROVIDER_ID, id); + } + + public String getProviderId() { + return (String) getUriParameter(PROVIDER_ID); + } + + public IdentityProviderForm form() { + return form; + } +} diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProvider.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProvider.java new file mode 100644 index 0000000000..32dadbafd3 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProvider.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 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.console.page.idp; + +import org.jboss.arquillian.graphene.page.Page; + +/** + * @author Vaclav Muzikar + */ +public class IdentityProvider extends IdentityProviders { + public static final String PROVIDER_ID = "id"; + public static final String ALIAS = "alias"; + + @Page + private IdentityProviderForm form; + + @Override + public String getUriFragment() { + return super.getUriFragment() + "/provider/{" + PROVIDER_ID + "}/{" + ALIAS + "}"; + } + + public void setIds(String providerId, String alias) { + setUriParameter(PROVIDER_ID, providerId); + setUriParameter(ALIAS, alias); + } + + public String getProviderId() { + return (String) getUriParameter(PROVIDER_ID); + } + + public String getAlias() { + return (String) getUriParameter(ALIAS); + } + + public IdentityProviderForm form() { + return form; + } +} diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProviderForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProviderForm.java new file mode 100644 index 0000000000..a6dd39abd4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProviderForm.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019 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.console.page.idp; + +import org.keycloak.testsuite.console.page.fragment.KcPassword; +import org.keycloak.testsuite.page.Form; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +import static org.keycloak.testsuite.util.UIUtils.setTextInputValue; + +/** + * @author Vaclav Muzikar + */ +public class IdentityProviderForm extends Form { + @FindBy(id = "clientId") + private WebElement clientIdInput; + + @FindBy(id = "clientSecret") + private KcPassword clientSecretInput; + + public void setClientId(final String value) { + setTextInputValue(clientIdInput, value); + } + + public void setClientSecret(final String value) { + clientSecretInput.setValue(value); + } + + public KcPassword clientSecret() { + return clientSecretInput; + } +} diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProviders.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProviders.java new file mode 100644 index 0000000000..a63d3e3d30 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/IdentityProviders.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 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.console.page.idp; + +import org.jboss.arquillian.graphene.fragment.Root; +import org.keycloak.testsuite.console.page.AdminConsoleRealm; +import org.keycloak.testsuite.console.page.fragment.DataTable; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.ui.Select; + +import static org.keycloak.testsuite.util.UIUtils.isElementVisible; +import static org.keycloak.testsuite.util.UIUtils.performOperationWithPageReload; + +/** + * @author Vaclav Muzikar + */ +public class IdentityProviders extends AdminConsoleRealm { + @FindBy(xpath = "//div[contains(@class,'blank-slate')]//select") + private Select addProviderBlankSlateSelect; + + @FindBy(tagName = "table") + private IdentityProvidersTable table; + + @Override + public String getUriFragment() { + return super.getUriFragment() + "/identity-provider-settings"; + } + + public IdentityProvidersTable table() { + return table; + } + + public void addProvider(final String providerId) { + Select idpSelect = table.isVisible() ? table.addProviderTableSelect : addProviderBlankSlateSelect; + performOperationWithPageReload(() -> idpSelect.selectByValue(providerId)); + } + + public class IdentityProvidersTable extends DataTable { + @Root + private WebElement tableRoot; + + @FindBy(tagName = "select") + private Select addProviderTableSelect; + + public boolean isVisible() { + return isElementVisible(tableRoot); + } + + public void clickProvider(final String alias) { + clickRowByLinkText(alias); + } + } +} diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/idp/IdentityProviderTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/idp/IdentityProviderTest.java index 72d899167c..258f7b079d 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/idp/IdentityProviderTest.java +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/idp/IdentityProviderTest.java @@ -18,48 +18,121 @@ package org.keycloak.testsuite.console.idp; import org.jboss.arquillian.graphene.page.Page; -import org.junit.Ignore; +import org.junit.Before; import org.junit.Test; import org.keycloak.testsuite.console.AbstractConsoleTest; -import org.keycloak.testsuite.console.page.idp.IdentityProviderSettings; -import org.keycloak.testsuite.model.Provider; -import org.keycloak.testsuite.model.SocialProvider; +import org.keycloak.testsuite.console.page.idp.CreateIdentityProvider; +import org.keycloak.testsuite.console.page.idp.IdentityProvider; +import org.keycloak.testsuite.console.page.idp.IdentityProviders; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals; /** * * @author Petr Mensik + * @author Vaclav Muzikar */ public class IdentityProviderTest extends AbstractConsoleTest { - @Page - private IdentityProviderSettings idpSettingsPage; + private IdentityProviders identityProvidersPage; -// @Test - public void testAddNewProvider() { - idpSettingsPage.addNewProvider(new Provider(SocialProvider.FACEBOOK, "klic", "secret")); - assertAlertSuccess(); - } + @Page + private IdentityProvider identityProviderPage; -// @Test(expected = NoSuchElementException.class) - public void testDuplicitProvider() { - idpSettingsPage.addNewProvider(new Provider(SocialProvider.FACEBOOK, "a", "b")); - } - -// @Test -// public void testEditProvider() { -// page.goToPage(SETTINGS_SOCIAL); -// page.editProvider(SocialProvider.FACEBOOK, new Provider(SocialProvider.FACEBOOK, "abc", "def")); -// } - -// @Test - public void testDeleteProvider() { + @Page + private CreateIdentityProvider createIdentityProviderPage; + @Before + public void beforeIdentityProviderTest() { + identityProvidersPage.navigateTo(); } @Test - @Ignore - public void testAddMultipleProviders() { + public void passwordMasking() { + createIdentityProviderPage.setProviderId("google"); + identityProviderPage.setIds("google", "google"); + + identityProvidersPage.addProvider("google"); + assertCurrentUrlEquals(createIdentityProviderPage); + + createIdentityProviderPage.form().setClientId("test-google"); + createIdentityProviderPage.form().setClientSecret("secret"); + assertEyeButtonIsEnabled(); + assertPasswordIsMasked(); + createIdentityProviderPage.form().clientSecret().clickEyeButton(); + assertPasswordIsUnmasked(); + createIdentityProviderPage.form().save(); + assertAlertSuccess(); + driver.navigate().refresh(); + assertCurrentUrlEquals(identityProviderPage); + + assertEyeButtonIsDisabled(); + assertPasswordIsMasked(); + identityProviderPage.form().setClientSecret("123456"); + assertEyeButtonIsEnabled(); + assertPasswordIsMasked(); + identityProviderPage.form().setClientSecret("${vault.fallout4}"); + assertEyeButtonIsDisabled(); + assertPasswordIsUnmasked(); + identityProviderPage.form().save(); + assertAlertSuccess(); + driver.navigate().refresh(); + assertCurrentUrlEquals(identityProviderPage); + + assertEyeButtonIsDisabled(); + assertPasswordIsUnmasked(); + identityProviderPage.form().setClientSecret("123456"); + assertEyeButtonIsEnabled(); + assertPasswordIsUnmasked(); + identityProviderPage.form().clientSecret().clickEyeButton(); + assertPasswordIsMasked(); } + + private void assertEyeButtonIsDisabled() { + assertTrue("Eye button is not disabled", identityProviderPage.form().clientSecret().isEyeButtonDisabled()); + } + + private void assertEyeButtonIsEnabled() { + assertFalse("Eye button is not enabled", identityProviderPage.form().clientSecret().isEyeButtonDisabled()); + } + + private void assertPasswordIsMasked() { + assertTrue("Password is not masked", identityProviderPage.form().clientSecret().isMasked()); + } + + private void assertPasswordIsUnmasked() { + assertFalse("Password is not unmasked", identityProviderPage.form().clientSecret().isMasked()); + } + +// @Page +// private IdentityProviderSettings idpSettingsPage; +// +//// @Test +// public void testAddNewProvider() { +// idpSettingsPage.addNewProvider(new Provider(SocialProvider.FACEBOOK, "klic", "secret")); +// assertAlertSuccess(); +// } +// +//// @Test(expected = NoSuchElementException.class) +// public void testDuplicitProvider() { +// idpSettingsPage.addNewProvider(new Provider(SocialProvider.FACEBOOK, "a", "b")); +// } +// +//// @Test +//// public void testEditProvider() { +//// page.goToPage(SETTINGS_SOCIAL); +//// page.editProvider(SocialProvider.FACEBOOK, new Provider(SocialProvider.FACEBOOK, "abc", "def")); +//// } +// +//// @Test +// public void testDeleteProvider() { +// +// } +// +// @Test +// @Ignore +// public void testAddMultipleProviders() { +// } } diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index 0530e405ee..8c7135c704 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -3170,9 +3170,54 @@ module.directive('kcPassword', function ($compile, Notifications) { return { restrict: 'A', link: function ($scope, elem, attr, ctrl) { + function toggleMask(evt) { + if(elem.hasClass('password-conceal')) { + view(); + } else { + conceal(); + } + } + + function view() { + elem.removeClass('password-conceal'); + + var t = elem.next().children().first(); + t.addClass('fa-eye-slash'); + t.removeClass('fa-eye'); + } + + function conceal() { + elem.addClass('password-conceal'); + + var t = elem.next().children().first(); + t.removeClass('fa-eye-slash'); + t.addClass('fa-eye'); + } + elem.addClass("password-conceal"); elem.attr("type","text"); elem.attr("autocomplete", "off"); + + var p = elem.parent(); + + var inputGroup = $('
'); + var eye = $('') + .on('click', toggleMask); + + $scope.$watch(attr.ngModel, function(v) { + if (v && v == '**********') { + elem.next().addClass('disabled') + } else if (v && v.indexOf('${v') == 0) { + elem.next().addClass('disabled') + view(); + } else { + elem.next().removeClass('disabled') + } + }) + + elem.detach().appendTo(inputGroup); + inputGroup.append(eye); + p.append(inputGroup); } } -}); \ No newline at end of file +});