KEYCLOAK-17179 IdP mappers with MultiValued property can't be saved

This commit is contained in:
Martin Bartoš 2021-04-20 15:09:53 +02:00 committed by Pavel Drozd
parent 91865fa93e
commit 07d57ca30f
8 changed files with 282 additions and 16 deletions

View file

@ -0,0 +1,78 @@
/*
* Copyright 2021 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.provider;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.List;
/**
* Testing IdP mapper with multivalued property
*
* @author Martin Bartos <mabartos@redhat.com>
*/
public class MultiValuedTestIdPMapper extends AbstractIdentityProviderMapper {
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
public static final String PROVIDER_ID = "multi-valued-test-idp-mapper";
public static final String VALUES_ATTRIBUTE = "values";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(VALUES_ATTRIBUTE);
property.setLabel("Test values");
property.setHelpText("Define test values");
property.setType(ProviderConfigProperty.MULTIVALUED_STRING_TYPE);
configProperties.add(property);
}
@Override
public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS;
}
@Override
public String getDisplayCategory() {
return "Test IdP Mapper";
}
@Override
public String getDisplayType() {
return "Test MultiValued Mapper";
}
@Override
public String getHelpText() {
return "This is testing IdP mapper with multivalued property";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View file

@ -0,0 +1,18 @@
#
# Copyright 2021 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.
#
org.keycloak.testsuite.broker.provider.MultiValuedTestIdPMapper

View file

@ -580,9 +580,9 @@ public class IdentityProviderTest extends AbstractAdminTest {
expected.add("hardcoded-user-session-attribute-idp-mapper");
expected.add("oidc-hardcoded-role-idp-mapper");
expected.add("hardcoded-attribute-idp-mapper");
for (String id: mapperIds) {
expected.add(id);
}
expected.add("multi-valued-test-idp-mapper");
expected.addAll(Arrays.asList(mapperIds));
Assert.assertEquals("mapperTypes", expected, mapperTypes.keySet());
}

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.testsuite.console.page.idp;
package org.keycloak.testsuite.console.page.idp.mappers;
import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.testsuite.console.page.AdminConsoleCreate;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.testsuite.console.page.idp;
package org.keycloak.testsuite.console.page.idp.mappers;
import org.keycloak.testsuite.page.Form;
import org.openqa.selenium.WebElement;
@ -34,6 +34,9 @@ public class IdentityProviderMapperForm extends Form {
@FindBy(id = "syncMode")
private Select syncMode;
@FindBy(id = "mapperTypeCreate")
private Select mapperType;
public void setName(final String value) {
setTextInputValue(name, value);
}
@ -45,4 +48,12 @@ public class IdentityProviderMapperForm extends Form {
public String syncMode() {
return syncMode.getFirstSelectedOption().getText();
}
public void setMapperType(final String value) {
mapperType.selectByVisibleText(value);
}
public String getMapperType() {
return mapperType.getFirstSelectedOption().getText();
}
}

View file

@ -0,0 +1,90 @@
/*
* Copyright 2021 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.mappers;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import java.util.List;
import java.util.NoSuchElementException;
import static org.keycloak.testsuite.util.UIUtils.getTextInputValue;
import static org.keycloak.testsuite.util.UIUtils.setTextInputValue;
/**
* @author mabartos
*/
public class MultivaluedStringProperty {
@FindBy(xpath = "//input[@ng-model='config[option.name][i]']")
private List<WebElement> items;
@FindBy(xpath = "//button[@data-ng-click='deleteValueFromMultivalued(option.name, $index)']")
private List<WebElement> minusButtons;
@FindBy(xpath = "//button[@data-ng-click='addValueToMultivalued(option.name)']")
private WebElement plusButton;
public boolean isPresent() {
try {
return plusButton.isDisplayed() && items != null && !items.isEmpty();
} catch (NoSuchElementException e) {
return false;
}
}
public void clickAddItem() {
plusButton.click();
}
public List<WebElement> getItems() {
return items;
}
public String getItem(int index) {
validateIndex(index);
return getTextInputValue(getItems().get(index));
}
public void editItem(int index, String item) {
validateIndex(index);
setTextInputValue(getItems().get(index), item);
}
public void addItem(String item) {
clickAddItem();
final List<WebElement> items = getItems();
WebElement webElement = items.get(items.size() - 1);
setTextInputValue(webElement, item);
}
public void removeItem(int index) {
validateIndex(index);
if (index == getItems().size() - 1) {
editItem(index, "");
} else {
minusButtons.get(index).click();
}
}
private void validateIndex(int index) {
if (index >= getItems().size()) throw new AssertionError("Input with index: " + index + " does not exist.");
}
}

View file

@ -20,18 +20,20 @@ package org.keycloak.testsuite.console.idp;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.testsuite.console.page.idp.mappers.MultivaluedStringProperty;
import org.keycloak.testsuite.console.AbstractConsoleTest;
import org.keycloak.testsuite.console.page.idp.CreateIdentityProvider;
import org.keycloak.testsuite.console.page.idp.CreateIdentityProviderMapper;
import org.keycloak.testsuite.console.page.idp.mappers.CreateIdentityProviderMapper;
import org.keycloak.testsuite.console.page.idp.IdentityProvider;
import org.keycloak.testsuite.console.page.idp.IdentityProviders;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.util.UIUtils.refreshPageAndWaitForLoad;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.hamcrest.Matchers.is;
/**
*
@ -52,6 +54,9 @@ public class IdentityProviderTest extends AbstractConsoleTest {
@Page
private CreateIdentityProviderMapper createIdentityProviderMapperPage;
@Page
private MultivaluedStringProperty multiStringPropertyForm;
@Before
public void beforeIdentityProviderTest() {
identityProvidersPage.navigateTo();
@ -128,6 +133,57 @@ public class IdentityProviderTest extends AbstractConsoleTest {
assertMapperSyncModeIsSetToImport();
}
@Test
public void createIdentityProviderCustomMapper() {
createIdentityProviderPage.setProviderId("google");
identityProviderPage.setIds("google", "google");
identityProvidersPage.addProvider("google");
assertCurrentUrlEquals(createIdentityProviderPage);
createIdentityProviderPage.form().setClientId("test-google");
createIdentityProviderPage.form().setClientSecret("secret");
createIdentityProviderPage.form().save();
assertAlertSuccess();
refreshPageAndWaitForLoad();
assertCurrentUrlEquals(identityProviderPage);
identityProviderPage.form().createMapper();
createIdentityProviderMapperPage.setIdp("google");
assertCurrentUrlEquals(createIdentityProviderMapperPage);
createIdentityProviderMapperPage.form().setName("Multivalued Map");
createIdentityProviderMapperPage.form().setSyncMode("import");
createIdentityProviderMapperPage.form().setMapperType("Test MultiValued Mapper");
assertThat(multiStringPropertyForm.isPresent(), is(true));
assertThat(multiStringPropertyForm.getItems().size(), is(1));
multiStringPropertyForm.editItem(0, "firstValue");
assertThat(multiStringPropertyForm.getItem(0), is("firstValue"));
multiStringPropertyForm.addItem("second");
assertThat(multiStringPropertyForm.getItems().size(), is(2));
multiStringPropertyForm.editItem(1, "secondValue");
assertThat(multiStringPropertyForm.getItem(1), is("secondValue"));
multiStringPropertyForm.addItem("third");
assertThat(multiStringPropertyForm.getItems().size(), is(3));
multiStringPropertyForm.removeItem(1);
assertThat(multiStringPropertyForm.getItems().size(), is(2));
assertThat(multiStringPropertyForm.getItem(1), is("third"));
createIdentityProviderMapperPage.form().save();
assertAlertSuccess();
// add empty item
assertThat(multiStringPropertyForm.getItems().size(), is(3));
refreshPageAndWaitForLoad();
assertThat(multiStringPropertyForm.getItems().size(), is(3));
}
private void assertMapperSyncModeIsSetToImport() {
assertEquals("import", createIdentityProviderMapperPage.form().syncMode());
}

View file

@ -2198,14 +2198,19 @@ module.controller('IdentityProviderMapperListCtrl', function($scope, realm, iden
$scope.mappers = mappers;
});
module.controller('IdentityProviderMapperCtrl', function($scope, realm, identityProvider, mapperTypes, mapper, IdentityProviderMapper, Notifications, Dialog, $location) {
module.controller('IdentityProviderMapperCtrl', function ($scope, realm, identityProvider, mapperTypes, mapper, IdentityProviderMapper, Notifications, Dialog, ComponentUtils, $location) {
$scope.realm = realm;
$scope.identityProvider = identityProvider;
$scope.create = false;
$scope.mapper = angular.copy(mapper);
$scope.changed = false;
$scope.mapperType = mapperTypes[mapper.identityProviderMapper];
$scope.$watch(function() {
ComponentUtils.convertAllMultivaluedStringValuesToList($scope.mapperType.properties, mapper.config);
ComponentUtils.addLastEmptyValueToMultivaluedLists($scope.mapperType.properties, mapper.config);
$scope.mapper = angular.copy(mapper);
$scope.$watch(function () {
return $location.path();
}, function() {
$scope.path = $location.path().substring(1).split("/");
@ -2218,12 +2223,16 @@ module.controller('IdentityProviderMapperCtrl', function($scope, realm, identit
}, true);
$scope.save = function() {
let mapperCopy = angular.copy($scope.mapper);
ComponentUtils.convertAllListValuesToMultivaluedString($scope.mapperType.properties, mapperCopy.config);
IdentityProviderMapper.update({
realm : realm.realm,
alias: identityProvider.alias,
alias : identityProvider.alias,
mapperId : mapper.id
}, $scope.mapper, function() {
}, mapperCopy, function () {
$scope.changed = false;
ComponentUtils.addLastEmptyValueToMultivaluedLists($scope.mapperType.properties, $scope.mapper.config);
mapper = angular.copy($scope.mapper);
$location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers/" + mapper.id);
Notifications.success("Your changes have been saved.");
@ -2251,7 +2260,7 @@ module.controller('IdentityProviderMapperCtrl', function($scope, realm, identit
});
module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, identityProvider, mapperTypes, IdentityProviderMapper, Notifications, Dialog, $location) {
module.controller('IdentityProviderMapperCreateCtrl', function ($scope, realm, identityProvider, mapperTypes, IdentityProviderMapper, Notifications, Dialog, ComponentUtils, $location) {
$scope.realm = realm;
$scope.identityProvider = identityProvider;
$scope.create = true;
@ -2268,11 +2277,15 @@ module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, id
$scope.path = $location.path().substring(1).split("/");
});
$scope.save = function() {
$scope.save = function () {
$scope.mapper.identityProviderMapper = $scope.mapperType.id;
let copyMapper = angular.copy($scope.mapper);
ComponentUtils.convertAllListValuesToMultivaluedString($scope.mapperType.properties, copyMapper.config);
IdentityProviderMapper.save({
realm : realm.realm, alias: identityProvider.alias
}, $scope.mapper, function(data, headers) {
realm : realm.realm,
alias : identityProvider.alias
}, copyMapper, function (data, headers) {
var l = headers().location;
var id = l.substring(l.lastIndexOf("/") + 1);
$location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers/" + id);