|  | @@ -352,14 +352,30 @@ public class SamlRealmTests extends SamlTestCase {
 | 
	
		
			
				|  |  |          final boolean principalIsEmailAddress = randomBoolean();
 | 
	
		
			
				|  |  |          final Boolean populateUserMetadata = randomFrom(Boolean.TRUE, Boolean.FALSE, null);
 | 
	
		
			
				|  |  |          final String authenticatingRealm = randomBoolean() ? REALM_NAME : null;
 | 
	
		
			
				|  |  | -        AuthenticationResult<User> result = performAuthentication(
 | 
	
		
			
				|  |  | -            roleMapper,
 | 
	
		
			
				|  |  | -            useNameId,
 | 
	
		
			
				|  |  | -            principalIsEmailAddress,
 | 
	
		
			
				|  |  | -            populateUserMetadata,
 | 
	
		
			
				|  |  | -            false,
 | 
	
		
			
				|  |  | -            authenticatingRealm
 | 
	
		
			
				|  |  | -        );
 | 
	
		
			
				|  |  | +        final boolean testWithDelimiter = randomBoolean();
 | 
	
		
			
				|  |  | +        final AuthenticationResult<User> result;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if (testWithDelimiter) {
 | 
	
		
			
				|  |  | +            result = performAuthentication(
 | 
	
		
			
				|  |  | +                roleMapper,
 | 
	
		
			
				|  |  | +                useNameId,
 | 
	
		
			
				|  |  | +                principalIsEmailAddress,
 | 
	
		
			
				|  |  | +                populateUserMetadata,
 | 
	
		
			
				|  |  | +                false,
 | 
	
		
			
				|  |  | +                authenticatingRealm,
 | 
	
		
			
				|  |  | +                List.of("STRIKE Team: Delta$shield"),
 | 
	
		
			
				|  |  | +                "$"
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            result = performAuthentication(
 | 
	
		
			
				|  |  | +                roleMapper,
 | 
	
		
			
				|  |  | +                useNameId,
 | 
	
		
			
				|  |  | +                principalIsEmailAddress,
 | 
	
		
			
				|  |  | +                populateUserMetadata,
 | 
	
		
			
				|  |  | +                false,
 | 
	
		
			
				|  |  | +                authenticatingRealm
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          assertThat(result, notNullValue());
 | 
	
		
			
				|  |  |          assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.SUCCESS));
 | 
	
		
			
				|  |  |          assertThat(result.getValue().principal(), equalTo(useNameId ? "clint.barton" : "cbarton"));
 | 
	
	
		
			
				|  | @@ -377,7 +393,11 @@ public class SamlRealmTests extends SamlTestCase {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          assertThat(userData.get().getUsername(), equalTo(useNameId ? "clint.barton" : "cbarton"));
 | 
	
		
			
				|  |  | -        assertThat(userData.get().getGroups(), containsInAnyOrder("avengers", "shield"));
 | 
	
		
			
				|  |  | +        if (testWithDelimiter) {
 | 
	
		
			
				|  |  | +            assertThat(userData.get().getGroups(), containsInAnyOrder("STRIKE Team: Delta", "shield"));
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            assertThat(userData.get().getGroups(), containsInAnyOrder("avengers", "shield"));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public void testAuthenticateWithAuthorizingRealm() throws Exception {
 | 
	
	
		
			
				|  | @@ -431,6 +451,28 @@ public class SamlRealmTests extends SamlTestCase {
 | 
	
		
			
				|  |  |          Boolean populateUserMetadata,
 | 
	
		
			
				|  |  |          boolean useAuthorizingRealm,
 | 
	
		
			
				|  |  |          String authenticatingRealm
 | 
	
		
			
				|  |  | +    ) throws Exception {
 | 
	
		
			
				|  |  | +        return performAuthentication(
 | 
	
		
			
				|  |  | +            roleMapper,
 | 
	
		
			
				|  |  | +            useNameId,
 | 
	
		
			
				|  |  | +            principalIsEmailAddress,
 | 
	
		
			
				|  |  | +            populateUserMetadata,
 | 
	
		
			
				|  |  | +            useAuthorizingRealm,
 | 
	
		
			
				|  |  | +            authenticatingRealm,
 | 
	
		
			
				|  |  | +            Arrays.asList("avengers", "shield"),
 | 
	
		
			
				|  |  | +            null
 | 
	
		
			
				|  |  | +        );
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private AuthenticationResult<User> performAuthentication(
 | 
	
		
			
				|  |  | +        UserRoleMapper roleMapper,
 | 
	
		
			
				|  |  | +        boolean useNameId,
 | 
	
		
			
				|  |  | +        boolean principalIsEmailAddress,
 | 
	
		
			
				|  |  | +        Boolean populateUserMetadata,
 | 
	
		
			
				|  |  | +        boolean useAuthorizingRealm,
 | 
	
		
			
				|  |  | +        String authenticatingRealm,
 | 
	
		
			
				|  |  | +        List<String> groups,
 | 
	
		
			
				|  |  | +        String groupsDelimiter
 | 
	
		
			
				|  |  |      ) throws Exception {
 | 
	
		
			
				|  |  |          final EntityDescriptor idp = mockIdp();
 | 
	
		
			
				|  |  |          final SpConfiguration sp = new SpConfiguration("<sp>", "https://saml/", null, null, null, Collections.emptyList());
 | 
	
	
		
			
				|  | @@ -453,8 +495,12 @@ public class SamlRealmTests extends SamlTestCase {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          final Settings.Builder settingsBuilder = Settings.builder()
 | 
	
		
			
				|  |  |              .put(getFullSettingKey(REALM_NAME, SamlRealmSettings.PRINCIPAL_ATTRIBUTE.getAttribute()), useNameId ? "nameid" : "uid")
 | 
	
		
			
				|  |  | -            .put(getFullSettingKey(REALM_NAME, SamlRealmSettings.GROUPS_ATTRIBUTE.getAttribute()), "groups")
 | 
	
		
			
				|  |  | +            .put(getFullSettingKey(REALM_NAME, SamlRealmSettings.GROUPS_ATTRIBUTE.getAttributeSetting().getAttribute()), "groups")
 | 
	
		
			
				|  |  |              .put(getFullSettingKey(REALM_NAME, SamlRealmSettings.MAIL_ATTRIBUTE.getAttribute()), "mail");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if (groupsDelimiter != null) {
 | 
	
		
			
				|  |  | +            settingsBuilder.put(getFullSettingKey(REALM_NAME, SamlRealmSettings.GROUPS_ATTRIBUTE.getDelimiter()), groupsDelimiter);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          if (principalIsEmailAddress) {
 | 
	
		
			
				|  |  |              final boolean anchoredMatch = randomBoolean();
 | 
	
		
			
				|  |  |              settingsBuilder.put(
 | 
	
	
		
			
				|  | @@ -497,7 +543,7 @@ public class SamlRealmTests extends SamlTestCase {
 | 
	
		
			
				|  |  |              randomAlphaOfLength(16),
 | 
	
		
			
				|  |  |              Arrays.asList(
 | 
	
		
			
				|  |  |                  new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.1", "uid", Collections.singletonList(uidValue)),
 | 
	
		
			
				|  |  | -                new SamlAttributes.SamlAttribute("urn:oid:1.3.6.1.4.1.5923.1.5.1.1", "groups", Arrays.asList("avengers", "shield")),
 | 
	
		
			
				|  |  | +                new SamlAttributes.SamlAttribute("urn:oid:1.3.6.1.4.1.5923.1.5.1.1", "groups", groups),
 | 
	
		
			
				|  |  |                  new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.3", "mail", Arrays.asList("cbarton@shield.gov"))
 | 
	
		
			
				|  |  |              )
 | 
	
		
			
				|  |  |          );
 | 
	
	
		
			
				|  | @@ -534,7 +580,120 @@ public class SamlRealmTests extends SamlTestCase {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    public void testAttributeSelectionWithRegex() throws Exception {
 | 
	
		
			
				|  |  | +    public void testAttributeSelectionWithSplit() {
 | 
	
		
			
				|  |  | +        List<String> strings = performAttributeSelectionWithSplit(",", "departments", "engineering", "elasticsearch-admins", "employees");
 | 
	
		
			
				|  |  | +        assertThat("For attributes: " + strings, strings, contains("engineering", "elasticsearch-admins", "employees"));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testAttributeSelectionWithSplitEmptyInput() {
 | 
	
		
			
				|  |  | +        List<String> strings = performAttributeSelectionWithSplit(",", "departments");
 | 
	
		
			
				|  |  | +        assertThat("For attributes: " + strings, strings, is(empty()));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testAttributeSelectionWithSplitJustDelimiter() {
 | 
	
		
			
				|  |  | +        List<String> strings = performAttributeSelectionWithSplit(",", ",");
 | 
	
		
			
				|  |  | +        assertThat("For attributes: " + strings, strings, is(empty()));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testAttributeSelectionWithSplitNoDelimiter() {
 | 
	
		
			
				|  |  | +        List<String> strings = performAttributeSelectionWithSplit(",", "departments", "elasticsearch-team");
 | 
	
		
			
				|  |  | +        assertThat("For attributes: " + strings, strings, contains("elasticsearch-team"));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private List<String> performAttributeSelectionWithSplit(String delimiter, String groupAttributeName, String... returnedGroups) {
 | 
	
		
			
				|  |  | +        final Settings settings = Settings.builder()
 | 
	
		
			
				|  |  | +            .put(REALM_SETTINGS_PREFIX + ".attributes.groups", groupAttributeName)
 | 
	
		
			
				|  |  | +            .put(REALM_SETTINGS_PREFIX + ".attribute_delimiters.groups", delimiter)
 | 
	
		
			
				|  |  | +            .build();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final RealmConfig config = buildConfig(settings);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final SamlRealmSettings.AttributeSettingWithDelimiter groupSetting = new SamlRealmSettings.AttributeSettingWithDelimiter("groups");
 | 
	
		
			
				|  |  | +        final SamlRealm.AttributeParser parser = SamlRealm.AttributeParser.forSetting(logger, groupSetting, config);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final SamlAttributes attributes = new SamlAttributes(
 | 
	
		
			
				|  |  | +            new SamlNameId(NameIDType.TRANSIENT, randomAlphaOfLength(24), null, null, null),
 | 
	
		
			
				|  |  | +            randomAlphaOfLength(16),
 | 
	
		
			
				|  |  | +            Collections.singletonList(
 | 
	
		
			
				|  |  | +                new SamlAttributes.SamlAttribute(
 | 
	
		
			
				|  |  | +                    "departments",
 | 
	
		
			
				|  |  | +                    "departments",
 | 
	
		
			
				|  |  | +                    Collections.singletonList(String.join(delimiter, returnedGroups))
 | 
	
		
			
				|  |  | +                )
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +        );
 | 
	
		
			
				|  |  | +        return parser.getAttribute(attributes);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testAttributeSelectionWithDelimiterAndPatternThrowsSettingsException() throws Exception {
 | 
	
		
			
				|  |  | +        final Settings settings = Settings.builder()
 | 
	
		
			
				|  |  | +            .put(REALM_SETTINGS_PREFIX + ".attributes.groups", "departments")
 | 
	
		
			
				|  |  | +            .put(REALM_SETTINGS_PREFIX + ".attribute_delimiters.groups", ",")
 | 
	
		
			
				|  |  | +            .put(REALM_SETTINGS_PREFIX + ".attribute_patterns.groups", "^(.+)@\\w+.example.com$")
 | 
	
		
			
				|  |  | +            .build();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final RealmConfig config = buildConfig(settings);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final SamlRealmSettings.AttributeSettingWithDelimiter groupSetting = new SamlRealmSettings.AttributeSettingWithDelimiter("groups");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final SettingsException settingsException = expectThrows(
 | 
	
		
			
				|  |  | +            SettingsException.class,
 | 
	
		
			
				|  |  | +            () -> SamlRealm.AttributeParser.forSetting(logger, groupSetting, config)
 | 
	
		
			
				|  |  | +        );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        assertThat(settingsException.getMessage(), containsString(REALM_SETTINGS_PREFIX + ".attribute_delimiters.groups"));
 | 
	
		
			
				|  |  | +        assertThat(settingsException.getMessage(), containsString(REALM_SETTINGS_PREFIX + ".attribute_patterns.groups"));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testAttributeSelectionNoGroupsConfiguredThrowsSettingsException() {
 | 
	
		
			
				|  |  | +        String delimiter = ",";
 | 
	
		
			
				|  |  | +        final Settings settings = Settings.builder().put(REALM_SETTINGS_PREFIX + ".attribute_delimiters.groups", delimiter).build();
 | 
	
		
			
				|  |  | +        final RealmConfig config = buildConfig(settings);
 | 
	
		
			
				|  |  | +        final SamlRealmSettings.AttributeSettingWithDelimiter groupSetting = new SamlRealmSettings.AttributeSettingWithDelimiter("groups");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final SettingsException settingsException = expectThrows(
 | 
	
		
			
				|  |  | +            SettingsException.class,
 | 
	
		
			
				|  |  | +            () -> SamlRealm.AttributeParser.forSetting(logger, groupSetting, config)
 | 
	
		
			
				|  |  | +        );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        assertThat(settingsException.getMessage(), containsString(REALM_SETTINGS_PREFIX + ".attribute_delimiters.groups"));
 | 
	
		
			
				|  |  | +        assertThat(settingsException.getMessage(), containsString(REALM_SETTINGS_PREFIX + ".attributes.groups"));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testAttributeSelectionWithSplitAndListThrowsSecurityException() {
 | 
	
		
			
				|  |  | +        String delimiter = ",";
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final Settings settings = Settings.builder()
 | 
	
		
			
				|  |  | +            .put(REALM_SETTINGS_PREFIX + ".attributes.groups", "departments")
 | 
	
		
			
				|  |  | +            .put(REALM_SETTINGS_PREFIX + ".attribute_delimiters.groups", delimiter)
 | 
	
		
			
				|  |  | +            .build();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final RealmConfig config = buildConfig(settings);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final SamlRealmSettings.AttributeSettingWithDelimiter groupSetting = new SamlRealmSettings.AttributeSettingWithDelimiter("groups");
 | 
	
		
			
				|  |  | +        final SamlRealm.AttributeParser parser = SamlRealm.AttributeParser.forSetting(logger, groupSetting, config);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        final SamlAttributes attributes = new SamlAttributes(
 | 
	
		
			
				|  |  | +            new SamlNameId(NameIDType.TRANSIENT, randomAlphaOfLength(24), null, null, null),
 | 
	
		
			
				|  |  | +            randomAlphaOfLength(16),
 | 
	
		
			
				|  |  | +            Collections.singletonList(
 | 
	
		
			
				|  |  | +                new SamlAttributes.SamlAttribute(
 | 
	
		
			
				|  |  | +                    "departments",
 | 
	
		
			
				|  |  | +                    "departments",
 | 
	
		
			
				|  |  | +                    List.of("engineering", String.join(delimiter, "elasticsearch-admins", "employees"))
 | 
	
		
			
				|  |  | +                )
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +        );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        ElasticsearchSecurityException securityException = expectThrows(
 | 
	
		
			
				|  |  | +            ElasticsearchSecurityException.class,
 | 
	
		
			
				|  |  | +            () -> parser.getAttribute(attributes)
 | 
	
		
			
				|  |  | +        );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        assertThat(securityException.getMessage(), containsString("departments"));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testAttributeSelectionWithRegex() {
 | 
	
		
			
				|  |  |          final boolean useFriendlyName = randomBoolean();
 | 
	
		
			
				|  |  |          final Settings settings = Settings.builder()
 | 
	
		
			
				|  |  |              .put(REALM_SETTINGS_PREFIX + ".attributes.principal", useFriendlyName ? "mail" : "urn:oid:0.9.2342.19200300.100.1.3")
 |