KEYCLOAK-19283 Implemented new identity provider mapper "Advanced claim to group mapper" alongside tests.
This commit is contained in:
parent
262cde8f52
commit
661aca4452
16 changed files with 778 additions and 4 deletions
|
@ -224,6 +224,21 @@ public class BrokeredIdentityContext {
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the set of groups that were assigned by mappers.
|
||||||
|
*
|
||||||
|
* @return a {@link Set} containing the groups.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Set<String> getMapperAssignedGroups() {
|
||||||
|
Set<String> groups = (Set<String>) this.contextData.get(Constants.MAPPER_GRANTED_GROUPS);
|
||||||
|
if (groups == null) {
|
||||||
|
groups = new HashSet<>();
|
||||||
|
this.contextData.put(Constants.MAPPER_GRANTED_GROUPS, groups);
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies if a mapper has already granted the specified role.
|
* Verifies if a mapper has already granted the specified role.
|
||||||
*
|
*
|
||||||
|
@ -234,6 +249,16 @@ public class BrokeredIdentityContext {
|
||||||
return this.getMapperGrantedRoles().contains(roleName);
|
return this.getMapperGrantedRoles().contains(roleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if a mapper has already assigned the specified group.
|
||||||
|
*
|
||||||
|
* @param groupId the id of the group.
|
||||||
|
* @return {@code true} if a mapper has already assigned the group; {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasMapperAssignedGroup(final String groupId) {
|
||||||
|
return this.getMapperAssignedGroups().contains(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the specified role to the set of roles granted by mappers.
|
* Adds the specified role to the set of roles granted by mappers.
|
||||||
*
|
*
|
||||||
|
@ -243,6 +268,15 @@ public class BrokeredIdentityContext {
|
||||||
this.getMapperGrantedRoles().add(roleName);
|
this.getMapperGrantedRoles().add(roleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the specified group to the set of groups assigned by mappers.
|
||||||
|
*
|
||||||
|
* @param groupId the id of the group.
|
||||||
|
*/
|
||||||
|
public void addMapperAssignedGroup(final String groupId) {
|
||||||
|
this.getMapperAssignedGroups().add(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use {@link #setFirstName(String)} and {@link #setLastName(String)} instead
|
* @deprecated use {@link #setFirstName(String)} and {@link #setLastName(String)} instead
|
||||||
* @param name
|
* @param name
|
||||||
|
|
|
@ -23,4 +23,5 @@ package org.keycloak.broker.provider;
|
||||||
*/
|
*/
|
||||||
public interface ConfigConstants {
|
public interface ConfigConstants {
|
||||||
String ROLE = "role";
|
String ROLE = "role";
|
||||||
|
String GROUP = "group";
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,9 @@ public final class Constants {
|
||||||
// Roles already granted by a mapper when updating brokered users.
|
// Roles already granted by a mapper when updating brokered users.
|
||||||
public static final String MAPPER_GRANTED_ROLES = "MAPPER_GRANTED_ROLES";
|
public static final String MAPPER_GRANTED_ROLES = "MAPPER_GRANTED_ROLES";
|
||||||
|
|
||||||
|
// Groups already assigned by a mapper when updating brokered users.
|
||||||
|
public static final String MAPPER_GRANTED_GROUPS = "MAPPER_GRANTED_GROUPS";
|
||||||
|
|
||||||
// Indication to admin-rest-endpoint that realm keys should be re-generated
|
// Indication to admin-rest-endpoint that realm keys should be re-generated
|
||||||
public static final String GENERATE = "GENERATE";
|
public static final String GENERATE = "GENERATE";
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ public class ProviderConfigProperty {
|
||||||
public static final String SCRIPT_TYPE="Script";
|
public static final String SCRIPT_TYPE="Script";
|
||||||
public static final String FILE_TYPE="File";
|
public static final String FILE_TYPE="File";
|
||||||
public static final String ROLE_TYPE="Role";
|
public static final String ROLE_TYPE="Role";
|
||||||
|
public static final String GROUP_TYPE="Group";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Possibility to configure single String value, which needs to be chosen from the list of predefined values (HTML select)
|
* Possibility to configure single String value, which needs to be chosen from the list of predefined values (HTML select)
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* 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.broker.oidc.mappers;
|
||||||
|
|
||||||
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
|
import org.keycloak.broker.provider.ConfigConstants;
|
||||||
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
|
public abstract class AbstractClaimToGroupMapper extends AbstractClaimMapper {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user,
|
||||||
|
IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
GroupModel group = this.getGroup(realm, mapperModel);
|
||||||
|
if (applies(mapperModel, context)) {
|
||||||
|
user.joinGroup(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user,
|
||||||
|
IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
GroupModel group = this.getGroup(realm, mapperModel);
|
||||||
|
String groupId = mapperModel.getConfig().get(ConfigConstants.GROUP);
|
||||||
|
|
||||||
|
if (!context.hasMapperAssignedGroup(groupId)) {
|
||||||
|
if (applies(mapperModel, context)) {
|
||||||
|
context.addMapperAssignedGroup(groupId);
|
||||||
|
user.joinGroup(group);
|
||||||
|
} else {
|
||||||
|
user.leaveGroup(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method must be implemented by subclasses and they must return {@code true} if their mapping can be applied
|
||||||
|
* (i.e. user has the OIDC claim that should be mapped) or {@code false} otherwise.
|
||||||
|
*
|
||||||
|
* @param mapperModel a reference to the {@link IdentityProviderMapperModel}.
|
||||||
|
* @param context a reference to the {@link BrokeredIdentityContext}.
|
||||||
|
* @return {@code true} if the mapping can be applied or {@code false} otherwise.*
|
||||||
|
*/
|
||||||
|
protected abstract boolean applies(final IdentityProviderMapperModel mapperModel,
|
||||||
|
final BrokeredIdentityContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the {@link GroupModel} corresponding the group configured in the specified
|
||||||
|
* {@link IdentityProviderMapperModel}.
|
||||||
|
* If the group doesn't exist, this method throws an {@link IdentityBrokerException}.
|
||||||
|
*
|
||||||
|
* @param realm a reference to the realm.
|
||||||
|
* @param mapperModel a reference to the {@link IdentityProviderMapperModel} containing the configured group.
|
||||||
|
* @return the {@link GroupModel}
|
||||||
|
* @throws IdentityBrokerException if the group doesn't exist.
|
||||||
|
*/
|
||||||
|
private GroupModel getGroup(final RealmModel realm, final IdentityProviderMapperModel mapperModel) {
|
||||||
|
GroupModel group = KeycloakModelUtils.findGroupByPath(realm, mapperModel.getConfig().get(ConfigConstants.GROUP));
|
||||||
|
|
||||||
|
if (group == null) {
|
||||||
|
throw new IdentityBrokerException("Unable to find group: " + group.getId());
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* 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.broker.oidc.mappers;
|
||||||
|
|
||||||
|
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||||
|
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||||
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
|
import org.keycloak.broker.provider.ConfigConstants;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
|
import org.keycloak.models.IdentityProviderSyncMode;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.keycloak.utils.RegexUtils.valueMatchesRegex;
|
||||||
|
|
||||||
|
public class AdvancedClaimToGroupMapper extends AbstractClaimToGroupMapper {
|
||||||
|
|
||||||
|
public static final String CLAIM_PROPERTY_NAME = "claims";
|
||||||
|
public static final String ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME = "are.claim.values.regex";
|
||||||
|
|
||||||
|
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty claimsProperty = new ProviderConfigProperty();
|
||||||
|
claimsProperty.setName(CLAIM_PROPERTY_NAME);
|
||||||
|
claimsProperty.setLabel("Claims");
|
||||||
|
claimsProperty.setHelpText("Name and value of the claims to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'. To use dot (.) literally, escape it with backslash (\\.)");
|
||||||
|
claimsProperty.setType(ProviderConfigProperty.MAP_TYPE);
|
||||||
|
configProperties.add(claimsProperty);
|
||||||
|
ProviderConfigProperty isClaimValueRegexProperty = new ProviderConfigProperty();
|
||||||
|
isClaimValueRegexProperty.setName(ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME);
|
||||||
|
isClaimValueRegexProperty.setLabel("Regex Claim Values");
|
||||||
|
isClaimValueRegexProperty.setHelpText("If enabled claim values are interpreted as regular expressions.");
|
||||||
|
isClaimValueRegexProperty.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||||
|
configProperties.add(isClaimValueRegexProperty);
|
||||||
|
ProviderConfigProperty groupProperty = new ProviderConfigProperty();
|
||||||
|
groupProperty.setName(ConfigConstants.GROUP);
|
||||||
|
groupProperty.setLabel("Group");
|
||||||
|
groupProperty.setHelpText("Group to assign the user to if claim is present.");
|
||||||
|
groupProperty.setType(ProviderConfigProperty.GROUP_TYPE);
|
||||||
|
configProperties.add(groupProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "oidc-advanced-group-idp-mapper";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getCompatibleProviders() {
|
||||||
|
return COMPATIBLE_PROVIDERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return "Group Importer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Advanced Claim to Group";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "If all claims exists, assign the user to the specified group.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean applies(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
Map<String, String> claims = mapperModel.getConfigMap(CLAIM_PROPERTY_NAME);
|
||||||
|
boolean areClaimValuesRegex = Boolean.parseBoolean(mapperModel.getConfig().get(ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME));
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> claim : claims.entrySet()) {
|
||||||
|
Object value = getClaimValue(context, claim.getKey());
|
||||||
|
|
||||||
|
boolean claimValuesMismatch = !(areClaimValuesRegex ? valueMatchesRegex(claim.getValue(), value) : valueEquals(claim.getValue(), value));
|
||||||
|
if (claimValuesMismatch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ org.keycloak.broker.provider.HardcodedAttributeMapper
|
||||||
org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper
|
org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper
|
||||||
org.keycloak.broker.oidc.mappers.ClaimToRoleMapper
|
org.keycloak.broker.oidc.mappers.ClaimToRoleMapper
|
||||||
org.keycloak.broker.oidc.mappers.AdvancedClaimToRoleMapper
|
org.keycloak.broker.oidc.mappers.AdvancedClaimToRoleMapper
|
||||||
|
org.keycloak.broker.oidc.mappers.AdvancedClaimToGroupMapper
|
||||||
org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper
|
org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper
|
||||||
org.keycloak.broker.oidc.mappers.UserAttributeMapper
|
org.keycloak.broker.oidc.mappers.UserAttributeMapper
|
||||||
org.keycloak.broker.oidc.mappers.UsernameTemplateMapper
|
org.keycloak.broker.oidc.mappers.UsernameTemplateMapper
|
||||||
|
|
|
@ -562,12 +562,12 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
create(createRep("keycloak-oidc", "keycloak-oidc"));
|
create(createRep("keycloak-oidc", "keycloak-oidc"));
|
||||||
provider = realm.identityProviders().get("keycloak-oidc");
|
provider = realm.identityProviders().get("keycloak-oidc");
|
||||||
mapperTypes = provider.getMapperTypes();
|
mapperTypes = provider.getMapperTypes();
|
||||||
assertMapperTypes(mapperTypes, "keycloak-oidc-role-to-role-idp-mapper", "oidc-user-attribute-idp-mapper", "oidc-role-idp-mapper", "oidc-username-idp-mapper", "oidc-advanced-role-idp-mapper");
|
assertMapperTypes(mapperTypes, "keycloak-oidc-role-to-role-idp-mapper", "oidc-user-attribute-idp-mapper", "oidc-role-idp-mapper", "oidc-username-idp-mapper", "oidc-advanced-group-idp-mapper", "oidc-advanced-role-idp-mapper");
|
||||||
|
|
||||||
create(createRep("oidc", "oidc"));
|
create(createRep("oidc", "oidc"));
|
||||||
provider = realm.identityProviders().get("oidc");
|
provider = realm.identityProviders().get("oidc");
|
||||||
mapperTypes = provider.getMapperTypes();
|
mapperTypes = provider.getMapperTypes();
|
||||||
assertMapperTypes(mapperTypes, "oidc-user-attribute-idp-mapper", "oidc-role-idp-mapper", "oidc-username-idp-mapper", "oidc-advanced-role-idp-mapper");
|
assertMapperTypes(mapperTypes, "oidc-user-attribute-idp-mapper", "oidc-role-idp-mapper", "oidc-username-idp-mapper", "oidc-advanced-group-idp-mapper", "oidc-advanced-role-idp-mapper");
|
||||||
|
|
||||||
create(createRep("saml", "saml"));
|
create(createRep("saml", "saml"));
|
||||||
provider = realm.identityProviders().get("saml");
|
provider = realm.identityProviders().get("saml");
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
|
||||||
|
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMapperTest {
|
||||||
|
|
||||||
|
private static final String CLAIMS_OR_ATTRIBUTES = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"key\": \"" + KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME + "\",\n" +
|
||||||
|
" \"value\": \"value 1\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"key\": \"" + KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2 + "\",\n" +
|
||||||
|
" \"value\": \"value 2\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
private static final String CLAIMS_OR_ATTRIBUTES_REGEX = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"key\": \"" + KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME + "\",\n" +
|
||||||
|
" \"value\": \"va.*\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"key\": \"" + KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2 + "\",\n" +
|
||||||
|
" \"value\": \"value 2\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
private String newValueForAttribute2 = "";
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void addMapperTestGroupToConsumerRealm() {
|
||||||
|
GroupRepresentation mapperTestGroup = new GroupRepresentation();
|
||||||
|
mapperTestGroup.setName(MAPPER_TEST_GROUP_NAME);
|
||||||
|
mapperTestGroup.setPath(MAPPER_TEST_GROUP_PATH);
|
||||||
|
|
||||||
|
adminClient.realm(bc.consumerRealmName()).groups().add(mapperTestGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allValuesMatch() {
|
||||||
|
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false);
|
||||||
|
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatUserHasBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void valuesMismatch() {
|
||||||
|
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false);
|
||||||
|
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value mismatch").build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatUserHasNotBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void valuesMatchIfNoClaimsSpecified() {
|
||||||
|
createAdvancedGroupMapper("[]", false);
|
||||||
|
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("some value").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("some value").build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatUserHasBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allValuesMatchRegex() {
|
||||||
|
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true);
|
||||||
|
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatUserHasBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void valuesMismatchRegex() {
|
||||||
|
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true);
|
||||||
|
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("mismatch").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatUserHasNotBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateBrokeredUserMismatchLeavesGroup() {
|
||||||
|
newValueForAttribute2 = "value mismatch";
|
||||||
|
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
|
||||||
|
|
||||||
|
assertThatUserHasNotBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateBrokeredUserMismatchDoesNotLeaveGroupInImportMode() {
|
||||||
|
newValueForAttribute2 = "value mismatch";
|
||||||
|
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false);
|
||||||
|
|
||||||
|
assertThatUserHasBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateBrokeredUserMatchDoesntLeaveGroup() {
|
||||||
|
newValueForAttribute2 = "value 2";
|
||||||
|
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
|
||||||
|
|
||||||
|
assertThatUserHasBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateBrokeredUserIsAssignedToGroupInForceModeWhenCreatingTheMapperAfterFirstLogin() {
|
||||||
|
newValueForAttribute2 = "value 2";
|
||||||
|
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, true);
|
||||||
|
|
||||||
|
assertThatUserHasBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserRepresentation createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) {
|
||||||
|
return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateUser() {
|
||||||
|
UserRepresentation user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
ImmutableMap<String, List<String>> matchingAttributes = ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add(newValueForAttribute2).build())
|
||||||
|
.put("some.other.attribute", ImmutableList.<String>builder().add("some value").build())
|
||||||
|
.build();
|
||||||
|
user.setAttributes(matchingAttributes);
|
||||||
|
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
|
||||||
|
createMapperInIdp(idp, CLAIMS_OR_ATTRIBUTES, false, syncMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createAdvancedGroupMapper(String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes) {
|
||||||
|
IdentityProviderRepresentation idp = setupIdentityProvider();
|
||||||
|
createMapperInIdp(idp, claimsOrAttributeRepresentation, areClaimsOrAttributeValuesRegexes, IMPORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected void createMapperInIdp(
|
||||||
|
IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode);
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
|
||||||
|
|
||||||
|
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
||||||
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class AbstractGroupMapperTest extends AbstractIdentityProviderMapperTest {
|
||||||
|
|
||||||
|
public static final String MAPPER_TEST_GROUP_NAME = "mapper-test";
|
||||||
|
public static final String MAPPER_TEST_GROUP_PATH = "/" + MAPPER_TEST_GROUP_NAME;
|
||||||
|
|
||||||
|
protected abstract void createMapperInIdp(
|
||||||
|
IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode);
|
||||||
|
|
||||||
|
protected void updateUser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected UserRepresentation loginAsUserTwiceWithMapper(
|
||||||
|
IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin,
|
||||||
|
Map<String, List<String>> userConfig) {
|
||||||
|
final IdentityProviderRepresentation idp = setupIdentityProvider();
|
||||||
|
if (!createAfterFirstLogin) {
|
||||||
|
createMapperInIdp(idp, syncMode);
|
||||||
|
}
|
||||||
|
createUserInProviderRealm(userConfig);
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
if (!createAfterFirstLogin) {
|
||||||
|
assertThatUserHasBeenAssignedToGroup(user);
|
||||||
|
} else {
|
||||||
|
assertThatUserHasNotBeenAssignedToGroup(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createAfterFirstLogin) {
|
||||||
|
createMapperInIdp(idp, syncMode);
|
||||||
|
}
|
||||||
|
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
|
||||||
|
|
||||||
|
updateUser();
|
||||||
|
|
||||||
|
logInAsUserInIDP();
|
||||||
|
user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertThatUserHasBeenAssignedToGroup(UserRepresentation user) {
|
||||||
|
List<String> groupNames = new ArrayList<>();
|
||||||
|
|
||||||
|
realm.users().get(user.getId()).groups().forEach(group -> {
|
||||||
|
groupNames.add(group.getName());
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(groupNames.contains(MAPPER_TEST_GROUP_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertThatUserHasNotBeenAssignedToGroup(UserRepresentation user) {
|
||||||
|
List<String> groupNames = new ArrayList<>();
|
||||||
|
|
||||||
|
realm.users().get(user.getId()).groups().forEach(group -> {
|
||||||
|
groupNames.add(group.getName());
|
||||||
|
});
|
||||||
|
|
||||||
|
assertFalse(groupNames.contains(MAPPER_TEST_GROUP_NAME));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
||||||
|
import org.keycloak.broker.oidc.mappers.AdvancedClaimToGroupMapper;
|
||||||
|
import org.keycloak.broker.provider.ConfigConstants;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
||||||
|
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||||
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
|
public class OidcAdvancedClaimToGroupMapperTest extends AbstractAdvancedGroupMapperTest {
|
||||||
|
@Override
|
||||||
|
protected BrokerConfiguration getBrokerConfiguration() {
|
||||||
|
return new KcOidcBrokerConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createMapperInIdp(IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation,
|
||||||
|
boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode) {
|
||||||
|
IdentityProviderMapperRepresentation advancedClaimToGroupMapper = new IdentityProviderMapperRepresentation();
|
||||||
|
advancedClaimToGroupMapper.setName("advanced-claim-to-group-mapper");
|
||||||
|
advancedClaimToGroupMapper.setIdentityProviderMapper(AdvancedClaimToGroupMapper.PROVIDER_ID);
|
||||||
|
advancedClaimToGroupMapper.setConfig(ImmutableMap.<String, String> builder()
|
||||||
|
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
|
||||||
|
.put(AdvancedClaimToGroupMapper.CLAIM_PROPERTY_NAME, claimsOrAttributeRepresentation)
|
||||||
|
.put(AdvancedClaimToGroupMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME,
|
||||||
|
areClaimsOrAttributeValuesRegexes ? "true" : "false")
|
||||||
|
.put(ConfigConstants.GROUP, MAPPER_TEST_GROUP_PATH)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
|
||||||
|
advancedClaimToGroupMapper.setIdentityProviderAlias(bc.getIDPAlias());
|
||||||
|
idpResource.addMapper(advancedClaimToGroupMapper).close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -253,6 +253,8 @@ aggregate.attrs.label=Aggregate attribute values
|
||||||
aggregate.attrs.tooltip=Indicates if attribute values should be aggregated with the group attributes. If using OpenID Connect mapper the multivalued option needs to be enabled too in order to get all the values. Duplicated values are discarded and the order of values is not guaranteed with this option.
|
aggregate.attrs.tooltip=Indicates if attribute values should be aggregated with the group attributes. If using OpenID Connect mapper the multivalued option needs to be enabled too in order to get all the values. Duplicated values are discarded and the order of values is not guaranteed with this option.
|
||||||
selectRole.label=Select Role
|
selectRole.label=Select Role
|
||||||
selectRole.tooltip=Enter role in the textbox to the left, or click this button to browse and select the role you want.
|
selectRole.tooltip=Enter role in the textbox to the left, or click this button to browse and select the role you want.
|
||||||
|
selectGroup.label=Select Group
|
||||||
|
selectGroup.tooltip=Enter group in the textbox to the left, or click this button to browse and select the group you want.
|
||||||
tokenClaimName.label=Token Claim Name
|
tokenClaimName.label=Token Claim Name
|
||||||
tokenClaimName.tooltip=Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created. To prevent nesting and use dot literally, escape the dot with backslash (\\.).
|
tokenClaimName.tooltip=Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created. To prevent nesting and use dot literally, escape the dot with backslash (\\.).
|
||||||
jsonType.label=Claim JSON Type
|
jsonType.label=Claim JSON Type
|
||||||
|
@ -1061,10 +1063,12 @@ export-clients=Export clients
|
||||||
|
|
||||||
action=Action
|
action=Action
|
||||||
role-selector=Role Selector
|
role-selector=Role Selector
|
||||||
|
group-selector=Group Selector
|
||||||
realm-roles.tooltip=Realm roles that can be selected.
|
realm-roles.tooltip=Realm roles that can be selected.
|
||||||
|
|
||||||
select-a-role=Select a role
|
select-a-role=Select a role
|
||||||
select-realm-role=Select realm role
|
select-realm-role=Select realm role
|
||||||
|
select-group=Select group
|
||||||
client-roles.tooltip=Client roles that can be selected.
|
client-roles.tooltip=Client roles that can be selected.
|
||||||
select-client-role=Select client role
|
select-client-role=Select client role
|
||||||
|
|
||||||
|
|
|
@ -3048,6 +3048,154 @@ module.controller('RoleSelectorModalCtrl', function($scope, realm, config, confi
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.controller('GroupSelectorModalCtrl', function($scope, $q, realm, config, configName, GroupsCount, Groups, Group, GroupChildren, Notifications, Dialog, ComponentUtils, $modalInstance, $translate) {
|
||||||
|
$scope.realm = realm;
|
||||||
|
$scope.groupList = [
|
||||||
|
{
|
||||||
|
"id" : "realm",
|
||||||
|
"name": $translate.instant('groups'),
|
||||||
|
"subGroups" : []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
$scope.groupSelector = {
|
||||||
|
searchCriteria: undefined,
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
numberOfPages: 1
|
||||||
|
};
|
||||||
|
$scope.groupSelector.currentPageInput = $scope.groupSelector.currentPage;
|
||||||
|
|
||||||
|
var refreshGroups = function (search) {
|
||||||
|
console.log('refreshGroups');
|
||||||
|
$scope.groupSelector.currentPageInput = $scope.groupSelector.currentPage;
|
||||||
|
|
||||||
|
var first = ($scope.groupSelector.currentPage * $scope.groupSelector.pageSize) - $scope.groupSelector.pageSize;
|
||||||
|
console.log('first:' + first);
|
||||||
|
var queryParams = {
|
||||||
|
realm : realm.realm,
|
||||||
|
first : first,
|
||||||
|
max : $scope.groupSelector.pageSize
|
||||||
|
};
|
||||||
|
var countParams = {
|
||||||
|
realm : realm.realm,
|
||||||
|
top : 'true'
|
||||||
|
};
|
||||||
|
|
||||||
|
if(angular.isDefined(search) && search !== '') {
|
||||||
|
queryParams.search = search;
|
||||||
|
countParams.search = search;
|
||||||
|
}
|
||||||
|
|
||||||
|
var promiseGetGroups = $q.defer();
|
||||||
|
Groups.query(queryParams, function(entry) {
|
||||||
|
promiseGetGroups.resolve(entry);
|
||||||
|
}, function() {
|
||||||
|
promiseGetGroups.reject($translate.instant('group.fetch.fail', {params: queryParams}));
|
||||||
|
});
|
||||||
|
promiseGetGroups.promise.then(function(groups) {
|
||||||
|
$scope.groupList = [
|
||||||
|
{
|
||||||
|
"id" : "realm",
|
||||||
|
"name": $translate.instant('groups'),
|
||||||
|
"subGroups": ComponentUtils.sortGroups('name', groups)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (angular.isDefined(search) && search !== '') {
|
||||||
|
// Add highlight for concrete text match
|
||||||
|
setTimeout(function () {
|
||||||
|
document.querySelectorAll('span').forEach(function (element) {
|
||||||
|
if (element.textContent.indexOf(search) != -1) {
|
||||||
|
angular.element(element).addClass('highlight');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}, function (failed) {
|
||||||
|
Notifications.error(failed);
|
||||||
|
});
|
||||||
|
|
||||||
|
var promiseCount = $q.defer();
|
||||||
|
console.log('countParams: realm[' + countParams.realm);
|
||||||
|
GroupsCount.query(countParams, function(entry) {
|
||||||
|
promiseCount.resolve(entry);
|
||||||
|
}, function() {
|
||||||
|
promiseCount.reject($translate.instant('group.fetch.fail', {params: countParams}));
|
||||||
|
});
|
||||||
|
promiseCount.promise.then(function(entry) {
|
||||||
|
if(angular.isDefined(entry.count) && entry.count > $scope.groupSelector.pageSize) {
|
||||||
|
$scope.groupSelector.numberOfPages = Math.ceil(entry.count/$scope.groupSelector.pageSize);
|
||||||
|
} else {
|
||||||
|
$scope.groupSelector.numberOfPages = 1;
|
||||||
|
}
|
||||||
|
}, function (failed) {
|
||||||
|
Notifications.error(failed);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
refreshGroups();
|
||||||
|
|
||||||
|
$scope.$watch('groupSelector.currentPage', function(newValue, oldValue) {
|
||||||
|
if(parseInt(newValue, 10) !== oldValue) {
|
||||||
|
refreshGroups($scope.groupSelector.searchCriteria);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.clearSearch = function() {
|
||||||
|
$scope.groupSelector.searchCriteria = '';
|
||||||
|
if (parseInt($scope.groupSelector.currentPage, 10) === 1) {
|
||||||
|
refreshGroups();
|
||||||
|
} else {
|
||||||
|
$scope.groupSelector.currentPage = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.searchGroup = function() {
|
||||||
|
if (parseInt($scope.groupSelector.currentPage, 10) === 1) {
|
||||||
|
refreshGroups($scope.groupSelector.searchCriteria);
|
||||||
|
} else {
|
||||||
|
$scope.groupSelector.currentPage = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.selectGroup = function(selected) {
|
||||||
|
if(!selected || selected.id === "realm") return;
|
||||||
|
|
||||||
|
config[configName] = selected.path;
|
||||||
|
$modalInstance.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.edit = $scope.selectGroup;
|
||||||
|
|
||||||
|
$scope.cancel = function() {
|
||||||
|
$modalInstance.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
var isLeaf = function(node) {
|
||||||
|
return node.id !== "realm" && (!node.subGroups || node.subGroups.length === 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getGroupClass = function(node) {
|
||||||
|
if (node.id === "realm") {
|
||||||
|
return 'pficon pficon-users';
|
||||||
|
}
|
||||||
|
if (isLeaf(node)) {
|
||||||
|
return 'normal';
|
||||||
|
}
|
||||||
|
if (node.subGroups.length && node.collapsed) return 'collapsed';
|
||||||
|
if (node.subGroups.length && !node.collapsed) return 'expanded';
|
||||||
|
return 'collapsed';
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getSelectedClass = function(node) {
|
||||||
|
if (node.selected) {
|
||||||
|
return 'selected';
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
module.controller('ProviderConfigCtrl', function ($modal, $scope, $route, ComponentUtils, Client) {
|
module.controller('ProviderConfigCtrl', function ($modal, $scope, $route, ComponentUtils, Client) {
|
||||||
clientSelectControl($scope, $route.current.params.realm, Client);
|
clientSelectControl($scope, $route.current.params.realm, Client);
|
||||||
$scope.fileNames = {};
|
$scope.fileNames = {};
|
||||||
|
@ -3091,6 +3239,24 @@ module.controller('ProviderConfigCtrl', function ($modal, $scope, $route, Compon
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.openGroupSelector = function (configName, config) {
|
||||||
|
$modal.open({
|
||||||
|
templateUrl: resourceUrl + '/partials/modal/group-selector.html',
|
||||||
|
controller: 'GroupSelectorModalCtrl',
|
||||||
|
resolve: {
|
||||||
|
realm: function () {
|
||||||
|
return $scope.realm;
|
||||||
|
},
|
||||||
|
config: function () {
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
configName: function () {
|
||||||
|
return configName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
$scope.changeClient = function(configName, config, client, multivalued) {
|
$scope.changeClient = function(configName, config, client, multivalued) {
|
||||||
if (!client || !client.id) {
|
if (!client || !client.id) {
|
||||||
config[configName] = null;
|
config[configName] = null;
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="cancel()">
|
||||||
|
<span class="pficon pficon-close"></span>
|
||||||
|
</button>
|
||||||
|
<h4 class="modal-title">{{:: 'group-selector' | translate}}</h4>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 0 15px 15px 15px;">
|
||||||
|
<form>
|
||||||
|
<table class="table table-striped table-bordered" style="margin-bottom: 0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="kc-table-actions" colspan="5">
|
||||||
|
<div class="form-inline">
|
||||||
|
<div class="pull-left">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" placeholder="{{:: 'search.placeholder' | translate}}" ng-model="groupSelector.searchCriteria" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('groupSearch').click()">
|
||||||
|
<div class="input-group-addon">
|
||||||
|
<i class="fa fa-search" id="groupSearch" ng-click="searchGroup()"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="viewAllGroups" class="btn btn-default" ng-click="clearSearch()">{{:: 'view-all-groups' | translate}}</button>
|
||||||
|
<div class="pull-right">
|
||||||
|
<button id="selectGroup" class="btn btn-default" ng-click="selectGroup(tree.currentNode)">{{:: 'selectGroup.label' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div
|
||||||
|
tree-id="tree"
|
||||||
|
angular-treeview="true"
|
||||||
|
tree-model="groupList"
|
||||||
|
node-id="id"
|
||||||
|
node-label="name"
|
||||||
|
node-children="subGroups" >
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div style="margin-bottom: 50px">
|
||||||
|
<kc-paging current-page="groupSelector.currentPage" number-of-pages="groupSelector.numberOfPages" current-page-input="groupSelector.currentPageInput"></kc-paging>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -29,6 +29,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6" data-ng-if="option.type == 'Group'">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<input class="form-control" type="text" data-ng-model="config[ option.name ]" >
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<button type="button" data-ng-click="openGroupSelector(option.name, config)" class="btn btn-default" tooltip-placement="top" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'selectGroup.tooltip' | translate}}">{{:: 'selectGroup.label' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-md-4" data-ng-if="option.type == 'ClientList'">
|
<div class="col-md-4" data-ng-if="option.type == 'ClientList'">
|
||||||
<input type="hidden" ui-select2="clientsUiSelect" id="clients" data-ng-init="initSelectedClient(option.name, config)" data-ng-model="selectedClient" data-ng-change="changeClient(option.name, config, selectedClient, false);" data-placeholder="{{:: 'selectOne' | translate}}...">
|
<input type="hidden" ui-select2="clientsUiSelect" id="clients" data-ng-init="initSelectedClient(option.name, config)" data-ng-model="selectedClient" data-ng-change="changeClient(option.name, config, selectedClient, false);" data-placeholder="{{:: 'selectOne' | translate}}...">
|
||||||
</input>
|
</input>
|
||||||
|
|
|
@ -45,7 +45,6 @@
|
||||||
var nodeChildren = attrs.nodeChildren || 'children';
|
var nodeChildren = attrs.nodeChildren || 'children';
|
||||||
|
|
||||||
//tree template
|
//tree template
|
||||||
|
|
||||||
var template =
|
var template =
|
||||||
'<ul>' +
|
'<ul>' +
|
||||||
'<li data-ng-repeat="node in ' + treeModel + '">' +
|
'<li data-ng-repeat="node in ' + treeModel + '">' +
|
||||||
|
@ -55,7 +54,6 @@
|
||||||
'</li>' +
|
'</li>' +
|
||||||
'</ul>';
|
'</ul>';
|
||||||
|
|
||||||
|
|
||||||
//check tree id, tree model
|
//check tree id, tree model
|
||||||
if( treeId && treeModel ) {
|
if( treeId && treeModel ) {
|
||||||
//root node
|
//root node
|
||||||
|
|
Loading…
Reference in a new issue