ldap port admin console

This commit is contained in:
Bill Burke 2016-11-08 12:30:20 -05:00
parent f138eecc27
commit 4880c0443c
14 changed files with 1011 additions and 15 deletions

View file

@ -19,6 +19,7 @@ package org.keycloak.storage.ldap;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
@ -33,6 +34,8 @@ import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderFactory;
import org.keycloak.storage.UserStorageProviderModel;
@ -83,10 +86,115 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
private static final Logger logger = Logger.getLogger(LDAPStorageProviderFactory.class);
public static final String PROVIDER_NAME = "ldap2";//LDAPConstants.LDAP_PROVIDER;
public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
private LDAPIdentityStoreRegistry ldapStoreRegistry;
protected static final List<ProviderConfigProperty> configProperties;
static {
configProperties = getConfigProps(null);
}
private static List<ProviderConfigProperty> getConfigProps(ComponentModel parent) {
boolean readOnly = false;
if (parent != null) {
LDAPConfig config = new LDAPConfig(parent.getConfig());
readOnly = config.getEditMode() != LDAPStorageProviderFactory.EditMode.WRITABLE;
}
return ProviderConfigurationBuilder.create()
.property().name(LDAPConstants.EDIT_MODE)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.SYNC_REGISTRATIONS)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false")
.add()
.property().name(LDAPConstants.VENDOR)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.USERNAME_LDAP_ATTRIBUTE)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.RDN_LDAP_ATTRIBUTE)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.UUID_LDAP_ATTRIBUTE)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.USER_OBJECT_CLASSES)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.CONNECTION_URL)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.USERS_DN)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.AUTH_TYPE)
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("simple")
.add()
.property().name(LDAPConstants.BIND_DN)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.BIND_CREDENTIAL)
.type(ProviderConfigProperty.PASSWORD)
.secret(true)
.add()
.property().name(LDAPConstants.CUSTOM_USER_SEARCH_FILTER)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(LDAPConstants.SEARCH_SCOPE)
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("1")
.add()
.property().name(LDAPConstants.USE_TRUSTSTORE_SPI)
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("ldapsOnly")
.add()
.property().name(LDAPConstants.CONNECTION_POOLING)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.add()
.property().name(LDAPConstants.PAGINATION)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.add()
.property().name(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false")
.add()
.property().name(KerberosConstants.SERVER_PRINCIPAL)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(KerberosConstants.KEYTAB)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(KerberosConstants.KERBEROS_REALM)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(KerberosConstants.DEBUG)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false")
.add()
.property().name(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false")
.add()
.property().name(KerberosConstants.SERVER_PRINCIPAL)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.build();
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public LDAPStorageProvider create(KeycloakSession session, ComponentModel model) {
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);

View file

@ -27,7 +27,9 @@ import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -60,8 +62,12 @@ public abstract class AbstractLDAPStorageMapperFactory implements LDAPStorageMap
}
@Override
public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
return new UserFederationMapperSyncConfigRepresentation(false, null, false, null);
public Map<String, Object> getTypeMetadata() {
Map<String, Object> metadata = new HashMap<>();
metadata.put("fedToKeycloakSyncSupported", false);
metadata.put("keycloakToFedSyncSupported", false);
return metadata;
}
@Override

View file

@ -44,8 +44,6 @@ public interface LDAPStorageMapperFactory<T extends LDAPStorageMapper> extends S
*/
T create(KeycloakSession session, ComponentModel model);
UserFederationMapperSyncConfigRepresentation getSyncConfig();
/**
* This is the name of the provider and will be showed in the admin console as an option.
*

View file

@ -176,10 +176,17 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact
}
@Override
public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
return new UserFederationMapperSyncConfigRepresentation(true, "sync-ldap-groups-to-keycloak", true, "sync-keycloak-groups-to-ldap");
public Map<String, Object> getTypeMetadata() {
Map<String, Object> metadata = new HashMap<>();
metadata.put("fedToKeycloakSyncSupported", true);
metadata.put("fedToKeycloakSyncMessage", "sync-ldap-groups-to-keycloak");
metadata.put("keycloakToFedSyncSupported", true);
metadata.put("keycloakToFedSyncMessage", "sync-keycloak-groups-to-ldap");
return metadata;
}
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", config);

View file

@ -168,10 +168,17 @@ public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFacto
}
@Override
public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
return new UserFederationMapperSyncConfigRepresentation(true, "sync-ldap-roles-to-keycloak", true, "sync-keycloak-roles-to-ldap");
public Map<String, Object> getTypeMetadata() {
Map<String, Object> metadata = new HashMap<>();
metadata.put("fedToKeycloakSyncSupported", true);
metadata.put("fedToKeycloakSyncMessage", "sync-ldap-roles-to-keycloak");
metadata.put("keycloakToFedSyncSupported", true);
metadata.put("keycloakToFedSyncMessage", "sync-keycloak-roles-to-ldap");
return metadata;
}
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", config);

View file

@ -25,6 +25,7 @@ import org.keycloak.provider.ProviderFactory;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -55,5 +56,17 @@ public interface ComponentFactory<CreatedType, ProviderType extends Provider> ex
return Collections.EMPTY_LIST;
}
/**
* This is metadata about this component type. Its really configuration information about the component type and not
* an individual instance
*
* @return
*/
default
Map<String, Object> getTypeMetadata() {
return Collections.EMPTY_MAP;
}
}

View file

@ -25,6 +25,7 @@ import org.keycloak.provider.ProviderFactory;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Useful when you want to describe config properties that are effected by the parent ComponentModel
@ -37,4 +38,16 @@ public interface SubComponentFactory<CreatedType, ProviderType extends Provider>
List<ProviderConfigProperty> getConfigProperties(RealmModel realm, ComponentModel parent) {
return getConfigProperties();
}
/**
* This is metadata about this component type. Its really configuration information about the component type and not
* an individual instance
*
* @return
*/
default Map<String, Object> getTypeMetadata(RealmModel realm, ComponentModel parent) {
return getTypeMetadata();
}
}

View file

@ -26,10 +26,13 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.user.ImportSynchronization;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -108,4 +111,14 @@ public interface UserStorageProviderFactory<T extends UserStorageProvider> exten
List<ProviderConfigProperty> getCommonProviderConfigProperties() {
return UserStorageProviderSpi.commonConfig();
}
@Override
default
Map<String, Object> getTypeMetadata() {
Map<String, Object> metadata = new HashMap<>();
if (this instanceof ImportSynchronization) {
metadata.put("synchronizable", true);
}
return metadata;
}
}

View file

@ -64,6 +64,8 @@ public class UserStorageProviderSpi implements Spi {
.property()
.name("lastSync").type(ProviderConfigProperty.STRING_TYPE).add()
.property()
.name("batchSizeForSync").type(ProviderConfigProperty.STRING_TYPE).add()
.property()
.name("importEnabled").type(ProviderConfigProperty.BOOLEAN_TYPE).add()
.property()
.name("cachePolicy").type(ProviderConfigProperty.STRING_TYPE).add()

View file

@ -20,6 +20,7 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.common.ClientConnection;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.component.SubComponentFactory;
@ -59,6 +60,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
@ -207,7 +209,7 @@ public class ComponentResource {
@Path("{id}/sub-component-config")
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public List<ConfigPropertyRepresentation> getSubcomponentConfig(@PathParam("id") String id, @QueryParam("type") String providerType, @QueryParam("id") String providerId) {
public ComponentTypeRepresentation getSubcomponentConfig(@PathParam("id") String id, @QueryParam("type") String providerType, @QueryParam("id") String providerId) {
auth.requireView();
ComponentModel parent = realm.getComponent(id);
if (parent == null) {
@ -224,9 +226,29 @@ public class ComponentResource {
throw new NotFoundException("Could not find subcomponent factory");
}
if (!(factory instanceof SubComponentFactory)) return Collections.EMPTY_LIST;
List<ProviderConfigProperty> props = ((SubComponentFactory)factory).getConfigProperties(realm, parent);
return ModelToRepresentation.toRepresentation(props);
if (!(factory instanceof ComponentFactory)) {
throw new NotFoundException("Not a component factory");
}
ComponentFactory componentFactory = (ComponentFactory)factory;
ComponentTypeRepresentation rep = new ComponentTypeRepresentation();
rep.setId(providerId);
rep.setHelpText(componentFactory.getHelpText());
List<ProviderConfigProperty> props = null;
Map<String, Object> metadata = null;
if (factory instanceof SubComponentFactory) {
props = ((SubComponentFactory)factory).getConfigProperties(realm, parent);
metadata = ((SubComponentFactory)factory).getTypeMetadata(realm, parent);
} else {
props = componentFactory.getConfigProperties();
metadata = componentFactory.getTypeMetadata();
}
List<ConfigPropertyRepresentation> propReps = ModelToRepresentation.toRepresentation(props);
rep.setProperties(propReps);
rep.setMetadata(metadata);
return rep;
}

View file

@ -20,6 +20,7 @@ package org.keycloak.services.resources.admin.info;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.component.ComponentFactory;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
@ -136,8 +137,8 @@ public class ServerInfoAdminResource {
List<ProviderConfigProperty> configProperties = configured.getConfigProperties();
if (configProperties == null) configProperties = Collections.EMPTY_LIST;
rep.setProperties(ModelToRepresentation.toRepresentation(configProperties));
if (pi instanceof ImportSynchronization) {
rep.getMetadata().put("synchronizable", true);
if (pi instanceof ComponentFactory) {
rep.setMetadata(((ComponentFactory)pi).getTypeMetadata());
}
List<ComponentTypeRepresentation> reps = info.getComponentTypes().get(spi.getProviderClass().getName());
if (reps == null) {

View file

@ -1469,6 +1469,26 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmSessionStatsCtrl'
})
.when('/create/user-storage/:realm/providers/ldap', {
templateUrl : resourceUrl + '/partials/user-storage-ldap.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
instance : function() {
return {
};
},
providerId : function($route) {
return $route.current.params.provider;
},
serverInfo : function(ServerInfoLoader) {
return ServerInfoLoader();
}
},
controller : 'LDAPUserStorageCtrl'
})
.when('/create/user-storage/:realm/providers/:provider', {
templateUrl : resourceUrl + '/partials/user-storage-generic.html',
resolve : {
@ -1489,6 +1509,24 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'GenericUserStorageCtrl'
})
.when('/realms/:realm/user-storage/providers/ldap/:componentId', {
templateUrl : resourceUrl + '/partials/user-storage-ldap.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
instance : function(ComponentLoader) {
return ComponentLoader();
},
providerId : function($route) {
return $route.current.params.provider;
},
serverInfo : function(ServerInfoLoader) {
return ServerInfoLoader();
}
},
controller : 'LDAPUserStorageCtrl'
})
.when('/realms/:realm/user-storage/providers/:provider/:componentId', {
templateUrl : resourceUrl + '/partials/user-storage-generic.html',
resolve : {

View file

@ -627,12 +627,14 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
for (var i = 0; i < $scope.providers.length; i++) {
$scope.providers[i].isUserFederationProvider = false;
}
/*
UserFederationProviders.query({realm: realm.realm}, function(data) {
for (var i = 0; i < data.length; i++) {
data[i].isUserFederationProvider = true;
$scope.providers.push(data[i]);
}
});
*/
$scope.addProvider = function(provider) {
console.log('Add provider: ' + provider.id);
@ -1553,4 +1555,321 @@ module.controller('UserGroupMembershipCtrl', function($scope, $route, realm, gro
});
module.controller('LDAPUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm,
serverInfo, instance, Components, UserStorageSync, RealmLDAPConnectionTester) {
console.log('LDAPUserStorageCtrl');
var providerId = 'ldap';
console.log('providerId: ' + providerId);
$scope.create = !instance.providerId;
console.log('create: ' + $scope.create);
var providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider'];
console.log('providers length ' + providers.length);
var providerFactory = null;
for (var i = 0; i < providers.length; i++) {
var p = providers[i];
console.log('provider: ' + p.id);
if (p.id == providerId) {
$scope.providerFactory = p;
providerFactory = p;
break;
}
}
$scope.provider = instance;
$scope.showSync = false;
$scope.ldapVendors = [
{ "id": "ad", "name": "Active Directory" },
{ "id": "rhds", "name": "Red Hat Directory Server" },
{ "id": "tivoli", "name": "Tivoli" },
{ "id": "edirectory", "name": "Novell eDirectory" },
{ "id": "other", "name": "Other" }
];
$scope.authTypes = [
{ "id": "none", "name": "none" },
{ "id": "simple", "name": "simple" }
];
$scope.searchScopes = [
{ "id": "1", "name": "One Level" },
{ "id": "2", "name": "Subtree" }
];
$scope.useTruststoreOptions = [
{ "id": "always", "name": "Always" },
{ "id": "ldapsOnly", "name": "Only for ldaps" },
{ "id": "never", "name": "Never" }
];
var DEFAULT_BATCH_SIZE = "1000";
console.log("providerFactory: " + providerFactory.id);
function initUserStorageSettings() {
if ($scope.create) {
instance.name = 'ldap';
instance.providerId = 'ldap';
instance.providerType = 'org.keycloak.storage.UserStorageProvider';
instance.parentId = realm.id;
instance.config = {
};
instance.config['priority'] = ["0"];
$scope.fullSyncEnabled = false;
$scope.changedSyncEnabled = false;
instance.config['fullSyncPeriod'] = ['-1'];
instance.config['changedSyncPeriod'] = ['-1'];
instance.config['cachePolicy'] = ['DEFAULT'];
instance.config['evictionDay'] = [''];
instance.config['evictionHour'] = [''];
instance.config['evictionMinute'] = [''];
instance.config['maxLifespan'] = [''];
instance.config['batchSizeForSync'] = [DEFAULT_BATCH_SIZE];
if (providerFactory.properties) {
for (var i = 0; i < providerFactory.properties.length; i++) {
var configProperty = providerFactory.properties[i];
if (configProperty.defaultValue) {
instance.config[configProperty.name] = [configProperty.defaultValue];
} else {
instance.config[configProperty.name] = [''];
}
}
}
} else {
$scope.fullSyncEnabled = (instance.config['fullSyncPeriod'] && instance.config['fullSyncPeriod'][0] > 0);
$scope.changedSyncEnabled = (instance.config['changedSyncPeriod'] && instance.config['changedSyncPeriod'][0]> 0);
if (!instance.config['fullSyncPeriod']) {
console.log('setting to -1');
instance.config['fullSyncPeriod'] = ['-1'];
}
if (!instance.config['changedSyncPeriod']) {
console.log('setting to -1');
instance.config['changedSyncPeriod'] = ['-1'];
}
if (!instance.config['cachePolicy']) {
instance.config['cachePolicy'] = ['DEFAULT'];
}
if (!instance.config['evictionDay']) {
instance.config['evictionDay'] = [''];
}
if (!instance.config['evictionHour']) {
instance.config['evictionHour'] = [''];
}
if (!instance.config['evictionMinute']) {
instance.config['evictionMinute'] = [''];
}
if (!instance.config['maxLifespan']) {
instance.config['maxLifespan'] = [''];
}
if (!instance.config['priority']) {
instance.config['priority'] = ['0'];
}
if (providerFactory.properties) {
for (var i = 0; i < providerFactory.properties.length; i++) {
var configProperty = providerFactory.properties[i];
if (!instance.config[configProperty.name]) {
if (configProperty.defaultValue) {
instance.config[configProperty.name] = [configProperty.defaultValue];
} else {
instance.config[configProperty.name] = [''];
}
}
}
}
for (var i=0 ; i<$scope.ldapVendors.length ; i++) {
if ($scope.ldapVendors[i].id === instance.config['vendor'][0]) {
$scope.vendorName = $scope.ldapVendors[i].name;
}
};
}
if (instance.config && instance.config['importEnabled']) {
$scope.showSync = instance.config['importEnabled'][0] == 'true';
} else {
$scope.showSync = true;
}
$scope.changed = false;
}
initUserStorageSettings();
$scope.instance = angular.copy(instance);
$scope.realm = realm;
$scope.$watch('instance', function() {
if (!angular.equals($scope.instance, instance)) {
$scope.changed = true;
}
if (!angular.equals($scope.instance.config['vendor'][0], $scope.lastVendor)) {
console.log("LDAP vendor changed");
$scope.lastVendor = $scope.instance.config['vendor'][0];
if ($scope.lastVendor === "ad") {
$scope.instance.config['usernameLDAPAttribute'][0] = "cn";
$scope.instance.config['userObjectClasses'][0] = "person, organizationalPerson, user";
} else {
$scope.instance.config['usernameLDAPAttribute'][0] = "uid";
$scope.instance.config['userObjectClasses'][0] = "inetOrgPerson, organizationalPerson";
}
$scope.instance.config['rdnLDAPAttribute'][0] = $scope.instance.config['usernameLDAPAttribute'][0];
var vendorToUUID = {
rhds: "nsuniqueid",
tivoli: "uniqueidentifier",
edirectory: "guid",
ad: "objectGUID",
other: "entryUUID"
};
$scope.instance.config['uuidLDAPAttribute'][0] = vendorToUUID[$scope.lastVendor];
}
}, true);
$scope.$watch('fullSyncEnabled', function(newVal, oldVal) {
if (oldVal == newVal) {
return;
}
$scope.instance.config['fullSyncPeriod'][0] = $scope.fullSyncEnabled ? "604800" : "-1";
$scope.changed = true;
});
$scope.$watch('changedSyncEnabled', function(newVal, oldVal) {
if (oldVal == newVal) {
return;
}
$scope.instance.config['changedSyncPeriod'][0] = $scope.changedSyncEnabled ? "86400" : "-1";
$scope.changed = true;
});
$scope.save = function() {
$scope.changed = false;
if (!parseInt($scope.instance.config['batchSizeForSync'[0]])) {
$scope.instance.config['batchSizeForSync'][0] = DEFAULT_BATCH_SIZE;
} else {
$scope.instance.config['batchSizeForSync'][0] = parseInt($scope.instance.config.batchSizeForSync).toString();
}
if ($scope.create) {
Components.save({realm: realm.realm}, $scope.instance, function (data, headers) {
var l = headers().location;
var id = l.substring(l.lastIndexOf("/") + 1);
$location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id);
Notifications.success("The provider has been created.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
} else {
Components.update({realm: realm.realm,
componentId: instance.id
},
$scope.instance, function () {
$route.reload();
Notifications.success("The provider has been updated.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
}
};
$scope.reset = function() {
initUserStorageSettings();
$scope.instance = angular.copy(instance);
};
$scope.cancel = function() {
if ($scope.create) {
$location.url("/realms/" + realm.realm + "/user-storage");
} else {
$route.reload();
}
};
$scope.triggerFullSync = function() {
console.log('GenericCtrl: triggerFullSync');
triggerSync('triggerFullSync');
}
$scope.triggerChangedUsersSync = function() {
console.log('GenericCtrl: triggerChangedUsersSync');
triggerSync('triggerChangedUsersSync');
}
function triggerSync(action) {
UserStorageSync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
$route.reload();
Notifications.success("Sync of users finished successfully. " + syncResult.status);
}, function() {
$route.reload();
Notifications.error("Error during sync of users");
});
}
var initConnectionTest = function(testAction, ldapConfig) {
return {
action: testAction,
realm: $scope.realm.realm,
connectionUrl: ldapConfig.connectionUrl,
bindDn: ldapConfig.bindDn,
bindCredential: ldapConfig.bindCredential,
useTruststoreSpi: ldapConfig.useTruststoreSpi
};
};
$scope.testConnection = function() {
console.log('LDAPCtrl: testConnection');
RealmLDAPConnectionTester.get(initConnectionTest("testConnection", $scope.instance.config), function() {
Notifications.success("LDAP connection successful.");
}, function() {
Notifications.error("Error when trying to connect to LDAP. See server.log for details.");
});
}
$scope.testAuthentication = function() {
console.log('LDAPCtrl: testAuthentication');
RealmLDAPConnectionTester.get(initConnectionTest("testAuthentication", $scope.instance.config), function() {
Notifications.success("LDAP authentication successful.");
}, function() {
Notifications.error("LDAP authentication failed. See server.log for details");
});
}
});

View file

@ -0,0 +1,449 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/user-federation">{{:: 'user-federation' | translate}}</a></li>
<li data-ng-hide="create">{{instance.name|capitalize}}</li>
<li data-ng-show="create">{{:: 'add-user-storage-provider' | translate}}</li>
</ol>
<kc-tabs-user-storage></kc-tabs-user-storage>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<input type="text" readonly value="this is not a login form" style="display: none;">
<input type="password" readonly value="this is not a login form" style="display: none;">
<fieldset>
<legend><span class="text">{{:: 'required-settings' | translate}}</span></legend>
<div class="form-group clearfix" data-ng-show="!create">
<label class="col-md-2 control-label" for="providerId">{{:: 'provider-id' | translate}} </label>
<div class="col-md-6">
<input class="form-control" id="providerId" type="text" ng-model="instance.id" readonly>
</div>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="consoleDisplayName">{{:: 'console-display-name' | translate}} </label>
<div class="col-md-6">
<input class="form-control" id="consoleDisplayName" type="text" ng-model="instance.name" placeholder="{{:: 'defaults-to-id' | translate}}">
</div>
<kc-tooltip>{{:: 'console-display-name.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="priority">{{:: 'priority' | translate}} </label>
<div class="col-md-6">
<input class="form-control" id="priority" type="text" ng-model="instance.config['priority'][0]">
</div>
<kc-tooltip>{{:: 'priority.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="editMode">{{:: 'edit-mode' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="editMode"
ng-model="instance.config['editMode'][0]">
<option>READ_ONLY</option>
<option>WRITABLE</option>
<option>UNSYNCED</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'ldap.edit-mode.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix block">
<label class="col-md-2 control-label" for="syncRegistrations">{{:: 'sync-registrations' | translate}}</label>
<div class="col-md-6">
<input ng-model="instance.config['syncRegistrations'][0]" name="syncRegistrations" id="syncRegistrations" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'ldap.sync-registrations.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="vendor"><span class="required">*</span> {{:: 'vendor' | translate}}</label>
<div class="col-md-6">
<div data-ng-show="create">
<select class="form-control" id="vendor"
ng-model="instance.config['vendor'][0]"
ng-options="vendor.id as vendor.name for vendor in ldapVendors"
required>
</select>
</div>
<div data-ng-show="!create">
<input class="form-control" id="vendor-ro" type="text" ng-model="vendorName" readonly>
</div>
</div>
<kc-tooltip>{{:: 'ldap.vendor.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="usernameLDAPAttribute"><span class="required">*</span> {{:: 'username-ldap-attribute' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="usernameLDAPAttribute" type="text" ng-model="instance.config['usernameLDAPAttribute'][0]" placeholder="{{:: 'ldap-attribute-name-for-username' | translate}}" required>
</div>
<kc-tooltip>{{:: 'username-ldap-attribute.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="rdnLDAPAttribute"><span class="required">*</span> {{:: 'rdn-ldap-attribute' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="rdnLDAPAttribute" type="text" ng-model="instance.config['rdnLDAPAttribute'][0]" placeholder="{{:: 'ldap-attribute-name-for-user-rdn' | translate}}" required>
</div>
<kc-tooltip>{{:: 'rdn-ldap-attribute.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="uuidLDAPAttribute"><span class="required">*</span> {{:: 'uuid-ldap-attribute' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="uuidLDAPAttribute" type="text" ng-model="instance.config['uuidLDAPAttribute'][0]" placeholder="{{:: 'ldap-attribute-name-for-uuid' | translate}}" required>
</div>
<kc-tooltip>{{:: 'uuid-ldap-attribute.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="userObjectClasses"><span class="required">*</span> {{:: 'user-object-classes' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="userObjectClasses" type="text" ng-model="instance.config['userObjectClasses'][0]" placeholder="{{:: 'ldap-user-object-classes.placeholder' | translate}}" required>
</div>
<kc-tooltip>{{:: 'ldap.user-object-classes.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="ldapConnectionUrl"><span class="required">*</span> {{:: 'connection-url' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="ldapConnectionUrl" type="text" ng-model="instance.config['connectionUrl'][0]" placeholder="{{:: 'ldap-connection-url' | translate}}" required>
</div>
<kc-tooltip>{{:: 'ldap.connection-url.tooltip' | translate}}</kc-tooltip>
<div class="col-sm-4" data-ng-show="access.manageRealm">
<a class="btn btn-primary" data-ng-click="testConnection()">{{:: 'test-connection' | translate}}</a>
</div>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="ldapUsersDn"><span class="required">*</span> {{:: 'users-dn' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="ldapUsersDn" type="text" ng-model="instance.config['usersDn'][0]" placeholder="{{:: 'ldap-users-dn' | translate}}" required>
</div>
<kc-tooltip>{{:: 'ldap.users-dn.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="authType"><span class="required">*</span> {{:: 'authentication-type' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="authType"
ng-model="instance.config['authType'][0]"
ng-options="authType.id as authType.name for authType in authTypes"
required>
</select>
</div>
</div>
<kc-tooltip>{{:: 'ldap.authentication-type.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-hide="instance.config['authType'][0] == 'none'">
<label class="col-md-2 control-label" for="ldapBindDn"><span class="required">*</span> {{:: 'bind-dn' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="ldapBindDn" type="text" ng-model="instance.config['bindDn'][0]" placeholder="{{:: 'ldap-bind-dn' | translate}}" data-ng-required="instance.config['authType'][0] != 'none'">
</div>
<kc-tooltip>{{:: 'ldap.bind-dn.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-hide="instance.config['authType'][0] == 'none'">
<label class="col-md-2 control-label" for="ldapBindCredential"><span class="required">*</span> {{:: 'bind-credential' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="ldapBindCredential" type="password" ng-model="instance.config['bindCredential'][0]" placeholder="{{:: 'ldap-bind-credentials' | translate}}" data-ng-required="instance.config['authType'][0] != 'none'">
</div>
<kc-tooltip>{{:: 'ldap.bind-credential.tooltip' | translate}}</kc-tooltip>
<div class="col-sm-4" data-ng-show="access.manageRealm">
<a class="btn btn-primary" data-ng-click="testAuthentication()">{{:: 'test-authentication' | translate}}</a>
</div>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="customUserSearchFilter">{{:: 'custom-user-ldap-filter' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="customUserSearchFilter" type="text" ng-model="instance.config['customUserSearchFilter'][0]" placeholder="{{:: 'ldap-filter' | translate}}">
</div>
<kc-tooltip>{{:: 'ldap.custom-user-ldap-filter.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="searchScope">{{:: 'search-scope' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="searchScope"
ng-model="instance.config['searchScope'][0]"
ng-options="searchScope.id as searchScope.name for searchScope in searchScopes"
required>
</select>
</div>
</div>
<kc-tooltip>{{:: 'ldap.search-scope.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="useTruststoreSpi">{{:: 'use-truststore-spi' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="useTruststoreSpi"
ng-model="instance.config['useTruststoreSpi'][0]"
ng-options="useTruststoreSpi.id as useTruststoreSpi.name for useTruststoreSpi in useTruststoreOptions"
required>
</select>
</div>
</div>
<kc-tooltip>{{:: 'ldap.use-truststore-spi.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="connectionPooling">{{:: 'connection-pooling' | translate}}</label>
<div class="col-md-6">
<input ng-model="instance.config['connectionPooling'][0]" name="connectionPooling" id="connectionPooling" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'ldap.connection-pooling.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="pagination">{{:: 'pagination' | translate}}</label>
<div class="col-md-6">
<input ng-model="instance.config['pagination'][0]" name="pagination" id="pagination" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'ldap.pagination.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset>
<legend><span class="text">{{:: 'kerberos-integration' | translate}}</span></legend>
<div class="form-group">
<label class="col-md-2 control-label" for="allowKerberosAuthentication">{{:: 'allow-kerberos-authentication' | translate}} </label>
<div class="col-md-6">
<input ng-model="instance.config['allowKerberosAuthentication'][0]" name="allowKerberosAuthentication" id="allowKerberosAuthentication" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'ldap.allow-kerberos-authentication.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['allowKerberosAuthentication'][0] == 'true'">
<label class="col-md-2 control-label" for="kerberosRealm"><span class="required">*</span> {{:: 'kerberos-realm' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="kerberosRealm" type="text" ng-model="instance.config['kerberosRealm'][0]" ng-required="instance.config['allowKerberosAuthentication'][0] == 'true'">
</div>
<kc-tooltip>{{:: 'kerberos-realm.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['allowKerberosAuthentication'][0] == 'true'">
<label class="col-md-2 control-label" for="serverPrincipal"><span class="required">*</span> {{:: 'server-principal' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="serverPrincipal" type="text" ng-model="instance.config['serverPrincipal'][0]" ng-required="instance.config['allowKerberosAuthentication'][0] == 'true'">
</div>
<kc-tooltip>{{:: 'server-principal.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['allowKerberosAuthentication'][0] == 'true'">
<label class="col-md-2 control-label" for="keyTab"><span class="required">*</span> {{:: 'keytab' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="keyTab" type="text" ng-model="instance.config['keyTab'][0]" ng-required="instance.config['allowKerberosAuthentication'][0] == 'true'">
</div>
<kc-tooltip>{{:: 'keytab.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="instance.config['allowKerberosAuthentication'][0] == 'true'">
<label class="col-md-2 control-label" for="debug">{{:: 'debug' | translate}} </label>
<div class="col-md-6">
<input ng-model="instance.config['debug'][0]" name="debug" id="debug" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'debug.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="instance.config['allowKerberosAuthentication'][0]">
<label class="col-md-2 control-label" for="useKerberosForPasswordAuthentication">{{:: 'use-kerberos-for-password-authentication' | translate}} </label>
<div class="col-md-6">
<input ng-model="instance.config['useKerberosForPasswordAuthentication'][0]" id="useKerberosForPasswordAuthentication" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'ldap.use-kerberos-for-password-authentication.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset>
<legend><span class="text">{{:: 'sync-settings' | translate}}</span></legend>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="batchSizeForSync">{{:: 'batch-size' | translate}}</label>
<div class="col-md-6">
<input class="form-control" type="text" ng-model="instance.config['batchSizeForSync'][0]" id="batchSizeForSync" />
</div>
<kc-tooltip>{{:: 'ldap.batch-size.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="fullSyncEnabled">{{:: 'periodic-full-sync' | translate}}</label>
<div class="col-md-6">
<input ng-model="fullSyncEnabled" name="fullSyncEnabled" id="fullSyncEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'ldap.periodic-full-sync.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="fullSyncEnabled">
<label class="col-md-2 control-label" for="fullSyncPeriod">{{:: 'full-sync-period' | translate}}</label>
<div class="col-md-6">
<input class="form-control" type="text" ng-model="instance.config['fullSyncPeriod'][0]" id="fullSyncPeriod" />
</div>
<kc-tooltip>{{:: 'full-sync-period.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="changedSyncEnabled">{{:: 'periodic-changed-users-sync' | translate}}</label>
<div class="col-md-6">
<input ng-model="changedSyncEnabled" name="changedSyncEnabled" id="changedSyncEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'ldap.periodic-changed-users-sync.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="changedSyncEnabled">
<label class="col-md-2 control-label" for="changedSyncPeriod">{{:: 'changed-users-sync-period' | translate}}</label>
<div class="col-md-6">
<input class="form-control" type="text" ng-model="instance.config['changedSyncPeriod'][0]" id="changedSyncPeriod" />
</div>
<kc-tooltip>{{:: 'ldap.changed-users-sync-period.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset>
<legend><span class="text">{{:: 'user-storage-cache-policy' | translate}}</span></legend>
<div class="form-group">
<label for="cachePolicy" class="col-md-2 control-label">{{:: 'userStorage.cachePolicy' | translate}}</label>
<div class="col-md-2">
<div>
<select id="cachePolicy" ng-model="instance.config['cachePolicy'][0]" class="form-control">
<option value="DEFAULT">{{:: 'userStorage.cachePolicy.option.DEFAULT' | translate}}</option>
<option value="EVICT_DAILY">{{:: 'userStorage.cachePolicy.option.EVICT_DAILY' | translate}}</option>
<option value="EVICT_WEEKLY">{{:: 'userStorage.cachePolicy.option.EVICT_WEEKLY' | translate}}</option>
<option value="MAX_LIFESPAN">{{:: 'userStorage.cachePolicy.option.MAX_LIFESPAN' | translate}}</option>
<option value="NO_CACHE">{{:: 'userStorage.cachePolicy.option.NO_CACHE' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'userStorage.cachePolicy.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="instance.config['cachePolicy'][0] == 'EVICT_WEEKLY'">
<label for="evictionDay" class="col-md-2 control-label">{{:: 'userStorage.evictionDay' | translate}}</label>
<div class="col-md-2">
<div>
<select id="evictionDay" ng-model="instance.config['evictionDay'][0]" class="form-control">
<option value="1">{{:: 'Sunday' | translate}}</option>
<option value="2">{{:: 'Monday' | translate}}</option>
<option value="3">{{:: 'Tuesday' | translate}}</option>
<option value="4">{{:: 'Wednesday' | translate}}</option>
<option value="5">{{:: 'Thursday' | translate}}</option>
<option value="6">{{:: 'Friday' | translate}}</option>
<option value="7">{{:: 'Saturday' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'userStorage.cachePolicy.evictionDay.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['cachePolicy'][0] == 'EVICT_WEEKLY' || instance.config['cachePolicy'][0] == 'EVICT_DAILY'">
<label class="col-md-2 control-label" for="evictionHour">{{:: 'userStorage.cachePolicy.evictionHour' | translate}}</label>
<div class="col-md-2">
<div>
<select id="evictionHour" ng-model="instance.config['evictionHour'][0]" class="form-control">
<option value="0">00</option>
<option value="1">01</option>
<option value="2">02</option>
<option value="3">03</option>
<option value="4">04</option>
<option value="5">05</option>
<option value="6">06</option>
<option value="7">07</option>
<option value="8">08</option>
<option value="9">09</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="17">17</option>
<option value="18">18</option>
<option value="19">19</option>
<option value="20">20</option>
<option value="21">21</option>
<option value="22">22</option>
<option value="23">23</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'userStorage.cachePolicy.evictionHour.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['cachePolicy'][0] == 'EVICT_WEEKLY' || instance.config['cachePolicy'][0] == 'EVICT_DAILY'">
<label class="col-md-2 control-label" for="evictionMinute">{{:: 'userStorage.cachePolicy.evictionMinute' | translate}}</label>
<div class="col-md-2">
<div>
<select id="evictionMinute" ng-model="instance.config['evictionMinute'][0]" class="form-control">
<option value="0">00</option>
<option value="1">01</option>
<option value="2">02</option>
<option value="3">03</option>
<option value="4">04</option>
<option value="5">05</option>
<option value="6">06</option>
<option value="7">07</option>
<option value="8">08</option>
<option value="9">09</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="17">17</option>
<option value="18">18</option>
<option value="19">19</option>
<option value="20">20</option>
<option value="21">21</option>
<option value="22">22</option>
<option value="23">23</option>
<option value="24">24</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
<option value="28">28</option>
<option value="29">29</option>
<option value="30">30</option>
<option value="31">31</option>
<option value="32">32</option>
<option value="33">33</option>
<option value="34">34</option>
<option value="35">35</option>
<option value="36">36</option>
<option value="37">37</option>
<option value="38">38</option>
<option value="39">39</option>
<option value="40">40</option>
<option value="41">41</option>
<option value="42">42</option>
<option value="43">43</option>
<option value="44">44</option>
<option value="45">45</option>
<option value="46">46</option>
<option value="47">47</option>
<option value="48">48</option>
<option value="49">49</option>
<option value="50">50</option>
<option value="51">51</option>
<option value="52">52</option>
<option value="53">53</option>
<option value="54">54</option>
<option value="55">55</option>
<option value="56">56</option>
<option value="57">57</option>
<option value="58">58</option>
<option value="59">59</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'userStorage.cachePolicy.evictionMinute.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="instance.config['cachePolicy'][0] == 'MAX_LIFESPAN'">
<label class="col-md-2 control-label" for="maxLifespan">{{:: 'userStorage.cachePolicy.maxLifespan' | translate}}</label>
<div class="col-md-6">
<input class="form-control" type="text" ng-model="instance.config['maxLifespan'][0]" id="maxLifespan" />
</div>
<kc-tooltip>{{:: 'userStorage.cachePolicy.maxLifespan.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<div class="form-group">
<div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageRealm">
<button kc-save>{{:: 'save' | translate}}</button>
<button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
</div>
</div>
<div class="form-group">
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageRealm">
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
<button class="btn btn-primary" data-ng-click="triggerChangedUsersSync()" data-ng-hide="changed">{{:: 'synchronize-changed-users' | translate}}</button>
<button class="btn btn-primary" data-ng-click="triggerFullSync()" data-ng-hide="changed">{{:: 'synchronize-all-users' | translate}}</button>
</div>
</div>
</form>
</div>
<kc-menu></kc-menu>