diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/LdapRealmSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/LdapRealmSettings.java index 4b746a79016..bd3e6dc187d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/LdapRealmSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/LdapRealmSettings.java @@ -46,7 +46,7 @@ public final class LdapRealmSettings { settings.addAll(LdapUserSearchSessionFactorySettings.getSettings()); settings.addAll(DelegatedAuthorizationSettings.getSettings(type)); } - settings.addAll(LdapMetaDataResolverSettings.getSettings()); + settings.addAll(LdapMetaDataResolverSettings.getSettings(type)); settings.addAll(RealmSettings.getStandardSettings(type)); return settings; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/support/LdapMetaDataResolverSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/support/LdapMetaDataResolverSettings.java index 878325f9837..9fa22aa0034 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/support/LdapMetaDataResolverSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/ldap/support/LdapMetaDataResolverSettings.java @@ -7,20 +7,18 @@ package org.elasticsearch.xpack.core.security.authc.ldap.support; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.xpack.core.security.authc.RealmSettings; -import org.elasticsearch.xpack.core.security.authc.ldap.LdapRealmSettings; import java.util.Collections; import java.util.List; import java.util.function.Function; public final class LdapMetaDataResolverSettings { - public static final Setting.AffixSetting> ADDITIONAL_META_DATA_SETTING = Setting.affixKeySetting( - RealmSettings.realmSettingPrefix(LdapRealmSettings.LDAP_TYPE), "metadata", - key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope)); + public static final Function>> ADDITIONAL_META_DATA_SETTING = RealmSettings.affixSetting( + "metadata", key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope)); private LdapMetaDataResolverSettings() {} - public static List> getSettings() { - return Collections.singletonList(ADDITIONAL_META_DATA_SETTING); + public static List> getSettings(String type) { + return Collections.singletonList(ADDITIONAL_META_DATA_SETTING.apply(type)); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java index df9a0a83261..0fb6e6c9cd9 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java @@ -9,6 +9,7 @@ import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.SimpleBindRequest; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.CharArrays; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.util.concurrent.AbstractRunnable; @@ -18,9 +19,7 @@ import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.ldap.LdapSessionFactorySettings; import org.elasticsearch.xpack.core.security.authc.ldap.SearchGroupsResolverSettings; -import org.elasticsearch.common.CharArrays; import org.elasticsearch.xpack.core.ssl.SSLService; -import org.elasticsearch.xpack.security.authc.ldap.support.LdapMetaDataResolver; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; import org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils; @@ -41,7 +40,6 @@ public class LdapSessionFactory extends SessionFactory { private final String[] userDnTemplates; private final GroupsResolver groupResolver; - private final LdapMetaDataResolver metaDataResolver; public LdapSessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) { super(config, sslService, threadPool); @@ -52,7 +50,6 @@ public class LdapSessionFactory extends SessionFactory { } logger.info("Realm [{}] is in user-dn-template mode: [{}]", config.name(), userDnTemplates); groupResolver = groupResolver(config); - metaDataResolver = new LdapMetaDataResolver(config, ignoreReferralErrors); } /** diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/PoolingSessionFactory.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/PoolingSessionFactory.java index 1312acae439..7891dbf1d0c 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/PoolingSessionFactory.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/PoolingSessionFactory.java @@ -15,6 +15,7 @@ import com.unboundid.ldap.sdk.SimpleBindRequest; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.CharArrays; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.lease.Releasable; import org.elasticsearch.common.settings.SecureString; @@ -24,9 +25,7 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings; -import org.elasticsearch.common.CharArrays; import org.elasticsearch.xpack.core.ssl.SSLService; -import org.elasticsearch.xpack.security.authc.ldap.support.LdapMetaDataResolver; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession; import org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils; import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; @@ -45,10 +44,8 @@ abstract class PoolingSessionFactory extends SessionFactory implements Releasabl private final LDAPConnectionPool connectionPool; final SimpleBindRequest bindCredentials; - final LdapMetaDataResolver metaDataResolver; final LdapSession.GroupsResolver groupResolver; - /** * @param config the configuration for the realm * @param sslService the ssl service to get a socket factory or context from @@ -63,7 +60,6 @@ abstract class PoolingSessionFactory extends SessionFactory implements Releasabl ThreadPool threadPool) throws LDAPException { super(config, sslService, threadPool); this.groupResolver = groupResolver; - this.metaDataResolver = new LdapMetaDataResolver(config, ignoreReferralErrors); final byte[] bindPassword; if (config.hasSetting(LEGACY_BIND_PASSWORD)) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java index 193254c7a39..d74b9ac3e3f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java @@ -60,6 +60,8 @@ public abstract class SessionFactory { protected final boolean sslUsed; protected final boolean ignoreReferralErrors; + protected final LdapMetaDataResolver metaDataResolver; + protected SessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) { this.config = config; this.logger = LogManager.getLogger(getClass()); @@ -78,6 +80,7 @@ public abstract class SessionFactory { this.serverSet = serverSet(config, sslService, ldapServers); this.sslUsed = ldapServers.ssl; this.ignoreReferralErrors = config.getSetting(SessionFactorySettings.IGNORE_REFERRAL_ERRORS_SETTING); + this.metaDataResolver = new LdapMetaDataResolver(config, ignoreReferralErrors); } /** diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java index 9fa731138b3..055b3b3d0cd 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java @@ -15,7 +15,9 @@ import com.unboundid.ldap.sdk.SingleServerSet; import com.unboundid.ldap.sdk.schema.Schema; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.client.Client; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; @@ -24,6 +26,9 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.license.TestUtils; import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.script.ScriptModule; +import org.elasticsearch.script.ScriptService; +import org.elasticsearch.script.mustache.MustacheScriptEngine; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; @@ -34,10 +39,13 @@ import org.elasticsearch.xpack.core.security.authc.ldap.ActiveDirectorySessionFa import org.elasticsearch.xpack.core.security.authc.ldap.LdapRealmSettings; import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings; import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapLoadBalancingSettings; +import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapMetaDataResolverSettings; import org.elasticsearch.xpack.core.security.authc.ldap.support.SessionFactorySettings; import org.elasticsearch.xpack.core.security.authc.support.CachingUsernamePasswordRealmSettings; import org.elasticsearch.xpack.core.security.authc.support.DnRoleMapperSettings; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; +import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping; +import org.elasticsearch.xpack.core.security.authc.support.mapper.TemplateRoleName; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings; import org.elasticsearch.xpack.core.ssl.SSLService; @@ -45,6 +53,8 @@ import org.elasticsearch.xpack.core.ssl.VerificationMode; import org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySessionFactory.DownLevelADAuthenticator; import org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySessionFactory.UpnADAuthenticator; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; +import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore; +import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -52,13 +62,13 @@ import org.junit.BeforeClass; import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey; -import static org.elasticsearch.xpack.core.security.authc.ldap.support.SessionFactorySettings.HOSTNAME_VERIFICATION_SETTING; import static org.elasticsearch.xpack.core.security.authc.ldap.support.SessionFactorySettings.URLS_SETTING; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; @@ -71,9 +81,11 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * Active Directory Realm tests that use the UnboundID In Memory Directory Server @@ -354,6 +366,62 @@ public class ActiveDirectoryRealmTests extends ESTestCase { assertThat(user.roles(), arrayContainingInAnyOrder(equalTo("group_role"), equalTo("user_role"))); } + /** + * This tests template role mappings (see + * {@link TemplateRoleName}) with an LDAP realm, using a additional + * metadata field (see {@link LdapMetaDataResolverSettings#ADDITIONAL_META_DATA_SETTING}). + */ + public void testRealmWithTemplatedRoleMapping() throws Exception { + final RealmConfig.RealmIdentifier realmId = realmId("testRealmWithTemplatedRoleMapping"); + Settings settings = settings(realmId, Settings.builder() + .put(getFullSettingKey(realmId, LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING), "departmentNumber") + .build()); + RealmConfig config = setupRealm(realmId, settings); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService, threadPool); + + SecurityIndexManager mockSecurityIndex = mock(SecurityIndexManager.class); + when(mockSecurityIndex.isAvailable()).thenReturn(true); + when(mockSecurityIndex.isIndexUpToDate()).thenReturn(true); + when(mockSecurityIndex.isMappingUpToDate()).thenReturn(true); + + Client mockClient = mock(Client.class); + when(mockClient.threadPool()).thenReturn(threadPool); + + final ScriptService scriptService = new ScriptService(settings, Collections.singletonMap(MustacheScriptEngine.NAME, + new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); + NativeRoleMappingStore roleMapper = new NativeRoleMappingStore(settings, mockClient, mockSecurityIndex, scriptService) { + @Override + protected void loadMappings(ActionListener> listener) { + listener.onResponse( + Arrays.asList( + this.buildMapping("m1", new BytesArray("{" + + "\"role_templates\":[{\"template\":{\"source\":\"_role_{{metadata.departmentNumber}}\"}}]," + + "\"enabled\":true," + + "\"rules\":{ " + + " \"field\":{\"realm.name\":\"testrealmwithtemplatedrolemapping\"}" + + "}}")))); + } + }; + LdapRealm realm = new LdapRealm(config, sessionFactory, roleMapper, threadPool); + realm.initialize(Collections.singleton(realm), licenseState); + + PlainActionFuture future = new PlainActionFuture<>(); + realm.authenticate(new UsernamePasswordToken("CN=Thor", new SecureString(PASSWORD)), future); + AuthenticationResult result = future.actionGet(); + assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS)); + User user = result.getUser(); + assertThat(user, notNullValue()); + assertThat(user.roles(), arrayContaining("_role_13")); + + future = new PlainActionFuture<>(); + realm.authenticate(new UsernamePasswordToken("CN=ironman", new SecureString(PASSWORD)), future); + result = future.actionGet(); + assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS)); + user = result.getUser(); + assertThat(user, notNullValue()); + assertThat(user.roles(), arrayContaining("_role_12")); + } + public void testRealmUsageStats() throws Exception { final RealmConfig.RealmIdentifier realmId = realmId("testRealmUsageStats"); String loadBalanceType = randomFrom("failover", "round_robin"); @@ -469,7 +537,8 @@ public class ActiveDirectoryRealmTests extends ESTestCase { builder.put(getFullSettingKey(realmIdentifier, SSLConfigurationSettings.VERIFICATION_MODE_SETTING_REALM), VerificationMode.CERTIFICATE); } else { - builder.put(getFullSettingKey(realmIdentifier, HOSTNAME_VERIFICATION_SETTING), false); + builder.put(getFullSettingKey(realmIdentifier, SSLConfigurationSettings.VERIFICATION_MODE_SETTING_REALM), + VerificationMode.NONE); } return builder.put(extraSettings).build(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java index 70e8719c0f7..a56c5550c65 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java @@ -416,7 +416,8 @@ public class LdapRealmTests extends LdapTestCase { Settings settings = Settings.builder() .put(defaultGlobalSettings) .put(buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE)) - .put(getFullSettingKey(REALM_IDENTIFIER.getName(), LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING), "uid") + .put(getFullSettingKey(REALM_IDENTIFIER.getName(), + LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING.apply(LdapRealmSettings.LDAP_TYPE)), "uid") .build(); RealmConfig config = getRealmConfig(REALM_IDENTIFIER, settings); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapMetaDataResolverTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapMetaDataResolverTests.java index bb54d6972bf..e2c2ae4aa2c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapMetaDataResolverTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapMetaDataResolverTests.java @@ -39,7 +39,8 @@ public class LdapMetaDataResolverTests extends ESTestCase { final RealmConfig.RealmIdentifier realmId = new RealmConfig.RealmIdentifier(LdapRealmSettings.LDAP_TYPE, "my_ldap"); final Settings settings = Settings.builder() .put("path.home", createTempDir()) - .putList(RealmSettings.getFullSettingKey(realmId.getName(), LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING), + .putList(RealmSettings.getFullSettingKey(realmId.getName(), + LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING.apply(LdapRealmSettings.LDAP_TYPE)), "cn", "uid") .build(); RealmConfig config = new RealmConfig(realmId, diff --git a/x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/ldap/ad.ldif b/x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/ldap/ad.ldif index dc4bd119892..6912cedd89e 100644 --- a/x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/ldap/ad.ldif +++ b/x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/ldap/ad.ldif @@ -29,6 +29,7 @@ userPrincipalName: ironman@ad.test.elasticsearch.com userPrincipalName: CN=ironman@ad.test.elasticsearch.com userPassword: password sn: Stark +departmentNumber: 12 dn: CN=Thor,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com objectclass: user @@ -42,3 +43,4 @@ tokenGroups:: AQUAAAAAAAUVAAAA4rc20emZjwwpdMkMUQQAAA== userPrincipalName: Thor@ad.test.elasticsearch.com userPassword: password sn: Stark +departmentNumber: 13 diff --git a/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/test/OpenLdapTests.java b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/test/OpenLdapTests.java index 61854a5396b..22515e2d793 100644 --- a/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/test/OpenLdapTests.java +++ b/x-pack/qa/openldap-tests/src/test/java/org/elasticsearch/test/OpenLdapTests.java @@ -228,7 +228,8 @@ public class OpenLdapTests extends ESTestCase { public void testResolveSingleValuedAttributeFromConnection() throws Exception { final RealmConfig.RealmIdentifier realmId = new RealmConfig.RealmIdentifier("ldap", "oldap-test"); final Settings settings = Settings.builder() - .putList(getFullSettingKey(realmId.getName(), LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING), "cn", "sn") + .putList(getFullSettingKey(realmId.getName(), LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING.apply("ldap")), + "cn", "sn") .build(); final RealmConfig config = new RealmConfig(realmId, settings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(Settings.EMPTY)); @@ -244,7 +245,8 @@ public class OpenLdapTests extends ESTestCase { public void testResolveMultiValuedAttributeFromConnection() throws Exception { final RealmConfig.RealmIdentifier realmId = new RealmConfig.RealmIdentifier("ldap", "oldap-test"); final Settings settings = Settings.builder() - .putList(getFullSettingKey(realmId.getName(), LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING), "objectClass") + .putList(getFullSettingKey(realmId.getName(), LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING.apply("ldap")), + "objectClass") .build(); final RealmConfig config = new RealmConfig(realmId, settings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(Settings.EMPTY)); @@ -260,7 +262,8 @@ public class OpenLdapTests extends ESTestCase { public void testResolveMissingAttributeFromConnection() throws Exception { final RealmConfig.RealmIdentifier realmId = new RealmConfig.RealmIdentifier("ldap", "oldap-test"); final Settings settings = Settings.builder() - .putList(getFullSettingKey(realmId.getName(), LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING), "alias") + .putList(getFullSettingKey(realmId.getName(), LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING.apply("ldap")), + "alias") .build(); final RealmConfig config = new RealmConfig(realmId, settings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(Settings.EMPTY));