Aligned users, users_roles and role_mapping file stores to behave like roles.yml

All three files are auto loaded by shield when modified. The behaviour that we agreed on is that when there's a parse failure in any of these files, we don't prevent the node from starting. Instead we skip the records that we failed to parse as if they don't exist. This is how `roles.yml` is handled today, and this commit makes sure that `users`,  `users_roles` and `role_mapping.yml` are aligned with this behaviour.

Also, the same behaviour is applied when the file is modified at runtime (so it's consistent with node start up).

This commit also adds a lot of missing tests for both `LdapGroupToRoleMapper` and `ActiveDirectoryGroupToRoleMapper` classes.

Original commit: elastic/x-pack-elasticsearch@7fdd6bb5cc
This commit is contained in:
uboness 2015-01-17 16:54:02 +01:00
parent d7d96d866e
commit 910d7c6372
9 changed files with 717 additions and 209 deletions

View File

@ -18,6 +18,7 @@ import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.RefreshListener;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.support.NoOpLogger;
import org.elasticsearch.shield.support.Validation;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
@ -46,7 +47,7 @@ public class FileUserPasswdStore {
private final Path file;
final Hasher hasher = Hasher.HTPASSWD;
private volatile ImmutableMap<String, char[]> esUsers;
private volatile ImmutableMap<String, char[]> users;
private CopyOnWriteArrayList<RefreshListener> listeners;
@ -57,8 +58,8 @@ public class FileUserPasswdStore {
FileUserPasswdStore(RealmConfig config, ResourceWatcherService watcherService, RefreshListener listener) {
logger = config.logger(FileUserPasswdStore.class);
file = resolveFile(config.settings(), config.env());
esUsers = parseFile(file, logger);
if (esUsers.isEmpty() && logger.isDebugEnabled()) {
users = parseFileLenient(file, logger);
if (users.isEmpty() && logger.isDebugEnabled()) {
logger.debug("realm [esusers] has no users");
}
FileWatcher watcher = new FileWatcher(file.getParent().toFile());
@ -74,15 +75,16 @@ public class FileUserPasswdStore {
listeners.add(listener);
}
int usersCount() {
return users.size();
}
public boolean verifyPassword(String username, SecuredString password) {
if (esUsers == null) {
if (users == null) {
return false;
}
char[] hash = esUsers.get(username);
if (hash == null) {
return false;
}
return hasher.verify(password, hash);
char[] hash = users.get(username);
return hash != null && hasher.verify(password, hash);
}
public static Path resolveFile(Settings settings, Environment env) {
@ -93,14 +95,29 @@ public class FileUserPasswdStore {
return Paths.get(location);
}
/**
* Internally in this class, we try to load the file, but if for some reason we can't, we're being more lenient by
* logging the error and skipping all users. This is aligned with how we handle other auto-loaded files in shield.
*/
static ImmutableMap<String, char[]> parseFileLenient(Path path, ESLogger logger) {
try {
return parseFile(path, logger);
} catch (Throwable t) {
logger.error("failed to parse users file [{}]. skipping/removing all users...", t, path.toAbsolutePath());
return ImmutableMap.of();
}
}
/**
* parses the esusers file. Should never return {@code null}, if the file doesn't exist an
* empty map is returned
*/
public static ImmutableMap<String, char[]> parseFile(Path path, @Nullable ESLogger logger) {
if (logger != null) {
logger.trace("reading users file located at [{}]", path.toAbsolutePath());
if (logger == null) {
logger = NoOpLogger.INSTANCE;
}
logger.trace("reading users file [{}]...", path.toAbsolutePath());
if (!Files.exists(path)) {
return ImmutableMap.of();
}
@ -122,17 +139,13 @@ public class FileUserPasswdStore {
}
int i = line.indexOf(":");
if (i <= 0 || i == line.length() - 1) {
if (logger != null) {
logger.error("invalid entry in users file [{}], line [{}]. skipping...", path.toAbsolutePath(), lineNr);
}
logger.error("invalid entry in users file [{}], line [{}]. skipping...", path.toAbsolutePath(), lineNr);
continue;
}
String username = line.substring(0, i).trim();
Validation.Error validationError = Validation.ESUsers.validateUsername(username);
if (validationError != null) {
if (logger != null) {
logger.error("invalid username [{}] in users file [{}], skipping... ({})", username, path.toAbsolutePath(), validationError);
}
logger.error("invalid username [{}] in users file [{}], skipping... ({})", username, path.toAbsolutePath(), validationError);
continue;
}
String hash = line.substring(i + 1).trim();
@ -140,7 +153,7 @@ public class FileUserPasswdStore {
}
ImmutableMap<String, char[]> usersMap = users.build();
if (logger != null && usersMap.isEmpty()){
if (usersMap.isEmpty()){
logger.warn("no users found in users file [{}]. use bin/shield/esusers to add users and role mappings", path.toAbsolutePath());
}
return usersMap;
@ -176,13 +189,8 @@ public class FileUserPasswdStore {
@Override
public void onFileChanged(File file) {
if (file.equals(FileUserPasswdStore.this.file.toFile())) {
try {
esUsers = parseFile(file.toPath(), logger);
logger.info("updated users (users file [{}] changed)", file.getAbsolutePath());
} catch (Throwable t) {
logger.error("failed to parse users file [{}]. current users remain unmodified", t, file.getAbsolutePath());
return;
}
logger.info("users file [{}] changed. updating users... )", file.getAbsolutePath());
users = parseFileLenient(file.toPath(), logger);
notifyRefresh();
}
}

View File

@ -16,6 +16,7 @@ import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.RefreshListener;
import org.elasticsearch.shield.support.NoOpLogger;
import org.elasticsearch.shield.support.Validation;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
@ -53,7 +54,7 @@ public class FileUserRolesStore {
FileUserRolesStore(RealmConfig config, ResourceWatcherService watcherService, RefreshListener listener) {
logger = config.logger(FileUserRolesStore.class);
file = resolveFile(config.settings(), config.env());
userRoles = parseFile(file, logger);
userRoles = parseFileLenient(file, logger);
FileWatcher watcher = new FileWatcher(file.getParent().toFile());
watcher.addListener(new FileListener());
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
@ -67,6 +68,10 @@ public class FileUserRolesStore {
listeners.add(listener);
}
int entriesCount() {
return userRoles.size();
}
public String[] roles(String username) {
if (userRoles == null) {
return Strings.EMPTY_ARRAY;
@ -83,15 +88,30 @@ public class FileUserRolesStore {
return Paths.get(location);
}
/**
* Internally in this class, we try to load the file, but if for some reason we can't, we're being more lenient by
* logging the error and skipping all enries. This is aligned with how we handle other auto-loaded files in shield.
*/
static ImmutableMap<String, String[]> parseFileLenient(Path path, ESLogger logger) {
try {
return parseFile(path, logger);
} catch (Throwable t) {
logger.error("failed to parse users_roles file [{}]. skipping/removing all entries...", t, path.toAbsolutePath());
return ImmutableMap.of();
}
}
/**
* parses the users_roles file. Should never return return {@code null}, if the file doesn't exist
* an empty map is returned. The read file holds a mapping per line of the form "role -> users" while the returned
* map holds entries of the form "user -> roles".
*/
public static ImmutableMap<String, String[]> parseFile(Path path, @Nullable ESLogger logger) {
if (logger != null) {
logger.trace("reading users roles file located at [{}]", path.toAbsolutePath());
if (logger == null) {
logger = NoOpLogger.INSTANCE;
}
logger.trace("reading users_roles file [{}]...", path.toAbsolutePath());
if (!Files.exists(path)) {
return ImmutableMap.of();
@ -114,31 +134,23 @@ public class FileUserRolesStore {
}
int i = line.indexOf(":");
if (i <= 0 || i == line.length() - 1) {
if (logger != null) {
logger.error("invalid entry in users_roles file [{}], line [{}]. skipping...", path.toAbsolutePath(), lineNr);
}
logger.error("invalid entry in users_roles file [{}], line [{}]. skipping...", path.toAbsolutePath(), lineNr);
continue;
}
String role = line.substring(0, i).trim();
Validation.Error validationError = Validation.Roles.validateRoleName(role);
if (validationError != null) {
if (logger != null) {
logger.error("invalid role entry in users_roles file [{}], line [{}] - {}. skipping...", path.toAbsolutePath(), lineNr, validationError);
}
logger.error("invalid role entry in users_roles file [{}], line [{}] - {}. skipping...", path.toAbsolutePath(), lineNr, validationError);
continue;
}
String usersStr = line.substring(i + 1).trim();
if (Strings.isEmpty(usersStr)) {
if (logger != null) {
logger.error("invalid entry for role [{}] in users_roles file [{}], line [{}]. no users found. skipping...", role, path.toAbsolutePath(), lineNr);
}
logger.error("invalid entry for role [{}] in users_roles file [{}], line [{}]. no users found. skipping...", role, path.toAbsolutePath(), lineNr);
continue;
}
String[] roleUsers = USERS_DELIM.split(usersStr);
if (roleUsers.length == 0) {
if (logger != null) {
logger.error("invalid entry for role [{}] in users_roles file [{}], line [{}]. no users found. skipping...", role, path.toAbsolutePath(), lineNr);
}
logger.error("invalid entry for role [{}] in users_roles file [{}], line [{}]. no users found. skipping...", role, path.toAbsolutePath(), lineNr);
continue;
}
@ -152,12 +164,17 @@ public class FileUserRolesStore {
}
}
ImmutableMap.Builder<String, String[]> usersRoles = ImmutableMap.builder();
ImmutableMap.Builder<String, String[]> builder = ImmutableMap.builder();
for (Map.Entry<String, List<String>> entry : userToRoles.entrySet()) {
usersRoles.put(entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()]));
builder.put(entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()]));
}
return usersRoles.build();
ImmutableMap<String, String[]> usersRoles = builder.build();
if (usersRoles.isEmpty()){
logger.warn("no entries found in users_roles file [{}]. use bin/shield/esusers to add users and role mappings", path.toAbsolutePath());
}
return usersRoles;
}
/**
@ -205,13 +222,8 @@ public class FileUserRolesStore {
@Override
public void onFileChanged(File file) {
if (file.equals(FileUserRolesStore.this.file.toFile())) {
try {
userRoles = parseFile(file.toPath(), logger);
logger.info("updated users (users_roles file [{}] changed)", file.getAbsolutePath());
} catch (Throwable t) {
logger.error("failed to parse users_roles file [{}]. current users_roles remain unmodified", t, file.getAbsolutePath());
return;
}
logger.info("users_roles file [{}] changed. updating users roles...", file.getAbsolutePath());
userRoles = parseFileLenient(file.toPath(), logger);
notifyRefresh();
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.RefreshListener;
import org.elasticsearch.shield.authc.support.ldap.AbstractGroupToRoleMapper;
import org.elasticsearch.watcher.ResourceWatcherService;
@ -14,7 +15,12 @@ import org.elasticsearch.watcher.ResourceWatcherService;
*/
public class LdapGroupToRoleMapper extends AbstractGroupToRoleMapper {
public LdapGroupToRoleMapper(RealmConfig config, ResourceWatcherService watcherService, RefreshListener listener) {
super(LdapRealm.TYPE, config, watcherService, listener);
}
public LdapGroupToRoleMapper(RealmConfig config, ResourceWatcherService watcherService) {
super(LdapRealm.TYPE, config, watcherService, null);
}
}

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.shield.authc.support.ldap;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.logging.ESLogger;
@ -13,6 +12,7 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.RefreshListener;
import org.elasticsearch.watcher.FileChangesListener;
@ -45,7 +45,7 @@ public abstract class AbstractGroupToRoleMapper {
private final String realmType;
private final Path file;
private final boolean useUnmappedGroupsAsRoles;
private volatile ImmutableMap<LdapName, Set<String>> groupRoles;
protected volatile ImmutableMap<LdapName, Set<String>> groupRoles;
private CopyOnWriteArrayList<RefreshListener> listeners;
@ -56,7 +56,7 @@ public abstract class AbstractGroupToRoleMapper {
useUnmappedGroupsAsRoles = config.settings().getAsBoolean(USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, false);
file = resolveFile(config.settings(), config.env());
groupRoles = parseFile(file, logger, realmType, config.name());
groupRoles = parseFileLenient(file, logger, realmType, config.name());
FileWatcher watcher = new FileWatcher(file.getParent().toFile());
watcher.addListener(new FileListener());
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
@ -78,7 +78,24 @@ public abstract class AbstractGroupToRoleMapper {
return Paths.get(location);
}
/**
* Internally in this class, we try to load the file, but if for some reason we can't, we're being more lenient by
* logging the error and skipping/removing all mappings. This is aligned with how we handle other auto-loaded files
* in shield.
*/
public static ImmutableMap<LdapName, Set<String>> parseFileLenient(Path path, ESLogger logger, String realmType, String realmName) {
try {
return parseFile(path, logger, realmType, realmName);
} catch (Throwable t) {
logger.error("failed to parse role mappings file [{}]. skipping/removing all mappings...", t, path.toAbsolutePath());
return ImmutableMap.of();
}
}
public static ImmutableMap<LdapName, Set<String>> parseFile(Path path, ESLogger logger, String realmType, String realmName) {
logger.trace("reading realm [{}/{}] role mappings file [{}]...", realmType, realmName, path.toAbsolutePath());
if (!Files.exists(path)) {
return ImmutableMap.of();
}
@ -101,18 +118,27 @@ public abstract class AbstractGroupToRoleMapper {
}
groupRoles.add(role);
} catch (InvalidNameException e) {
logger.error("invalid group DN [{}] found in [{}] group to role mappings [{}] for realm [{}]. skipping... ", e, ldapDN, realmType, path.toAbsolutePath(), realmName);
logger.error("invalid group DN [{}] found in [{}] group to role mappings [{}] for realm [{}/{}]. skipping... ", e, ldapDN, realmType, path.toAbsolutePath(), realmType, realmName);
}
}
}
if (groupToRoles.isEmpty()){
logger.warn("no mappings found in role mappings file [{}] for realm [{}/{}]", path.toAbsolutePath(), realmType, realmName);
}
return ImmutableMap.copyOf(groupToRoles);
} catch (IOException e) {
throw new ElasticsearchException("unable to load [" + realmName + "] role mapper file [" + path.toAbsolutePath() + "]", e);
throw new ShieldSettingsException("could not read realm [" + realmType + "/" + realmName + "] role mappings file [" + path.toAbsolutePath() + "]", e);
}
}
int mappingsCount() {
return groupRoles.size();
}
/**
* This will map the groupDN's to ES Roles
*/
@ -127,7 +153,7 @@ public abstract class AbstractGroupToRoleMapper {
}
}
if (logger.isDebugEnabled()) {
logger.debug("the roles [{}], are mapped from these [{}] groups [{}] for realm [{}]", roles, realmType, groupDns, config.name());
logger.debug("the roles [{}], are mapped from these [{}] groups [{}] for realm [{}/{}]", roles, realmType, groupDns, realmType, config.name());
}
return roles;
}
@ -156,13 +182,8 @@ public abstract class AbstractGroupToRoleMapper {
@Override
public void onFileChanged(File file) {
if (file.equals(AbstractGroupToRoleMapper.this.file.toFile())) {
try {
groupRoles = parseFile(file.toPath(), logger, realmType, config.name());
logger.info("updated role mappings (role mappings file [{}] changed) for realm [{}]", file.getAbsolutePath(), config.name());
} catch (Throwable t) {
logger.error("could not reload role mappings file [{}] for realm [{}]. current role mappings remain unmodified", t, file.getAbsolutePath(), config.name());
return;
}
logger.info("role mappings file [{}] changed for realm [{}/{}]. updating mappings...", file.getAbsolutePath(), realmType, config.name());
groupRoles = parseFileLenient(file.toPath(), logger, realmType, config.name());
notifyRefresh();
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.active_directory;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.ldap.AbstractGroupToRoleMapper;
import org.elasticsearch.shield.authc.support.ldap.AbstractGroupToRoleMapperTests;
import org.elasticsearch.watcher.ResourceWatcherService;
import java.nio.file.Path;
/**
*
*/
public class ActiveDirectoryGroupToRoleMapperTests extends AbstractGroupToRoleMapperTests {
@Override
protected AbstractGroupToRoleMapper createMapper(Path file, ResourceWatcherService watcherService) {
Settings adSettings = ImmutableSettings.builder()
.put("files.role_mapping", file.toAbsolutePath())
.build();
RealmConfig config = new RealmConfig("ad-group-mapper-test", adSettings, settings, env);
return new ActiveDirectoryGroupToRoleMapper(config, watcherService);
}
}

View File

@ -5,12 +5,16 @@
*/
package org.elasticsearch.shield.authc.esusers;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.ShieldException;
import org.elasticsearch.shield.audit.logfile.CapturingLogger;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.RefreshListener;
@ -18,6 +22,8 @@ import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.BufferedWriter;
@ -26,6 +32,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -39,6 +46,117 @@ import static org.mockito.Mockito.*;
*/
public class FileUserPasswdStoreTests extends ElasticsearchTestCase {
private Settings settings;
private Environment env;
private ThreadPool threadPool;
@Before
public void init() {
settings = ImmutableSettings.builder()
.put("watcher.interval.high", "2s")
.build();
env = new Environment(settings);
threadPool = new ThreadPool("test");
}
@After
public void shutdown() {
threadPool.shutdownNow();
}
@Test
public void testStore_ConfiguredWithUnreadableFile() throws Exception {
Path file = newTempFile().toPath();
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
Files.write(file, ImmutableList.of("aldlfkjldjdflkjd"), Charsets.UTF_16);
Settings esusersSettings = ImmutableSettings.builder()
.put("files.users", file.toAbsolutePath())
.build();
RealmConfig config = new RealmConfig("esusers-test", esusersSettings, settings, env);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService);
assertThat(store.usersCount(), is(0));
}
@Test
public void testStore_AutoReload() throws Exception {
Path users = Paths.get(getClass().getResource("users").toURI());
Path tmp = Files.createTempFile(null, null);
Files.copy(users, Files.newOutputStream(tmp));
Settings esusersSettings = ImmutableSettings.builder()
.put("files.users", tmp.toAbsolutePath())
.build();
RealmConfig config = new RealmConfig("esusers-test", esusersSettings, settings, env);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
final CountDownLatch latch = new CountDownLatch(1);
FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService, new RefreshListener() {
@Override
public void onRefresh() {
latch.countDown();
}
});
assertThat(store.verifyPassword("bcrypt", SecuredStringTests.build("test123")), is(true));
watcherService.start();
try (BufferedWriter writer = Files.newBufferedWriter(tmp, Charsets.UTF_8, StandardOpenOption.APPEND)) {
writer.newLine();
writer.append("foobar:").append(new String(Hasher.HTPASSWD.hash(SecuredStringTests.build("barfoo"))));
}
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("Waited too long for the updated file to be picked up");
}
assertThat(store.verifyPassword("foobar", SecuredStringTests.build("barfoo")), is(true));
}
@Test
public void testStore_AutoReload_WithParseFailures() throws Exception {
Path users = Paths.get(getClass().getResource("users").toURI());
Path tmp = Files.createTempFile(null, null);
Files.copy(users, Files.newOutputStream(tmp));
Settings esusersSettings = ImmutableSettings.builder()
.put("files.users", tmp.toAbsolutePath())
.build();
RealmConfig config = new RealmConfig("esusers-test", esusersSettings, settings, env);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
final CountDownLatch latch = new CountDownLatch(1);
FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService, new RefreshListener() {
@Override
public void onRefresh() {
latch.countDown();
}
});
assertTrue(store.verifyPassword("bcrypt", SecuredStringTests.build("test123")));
watcherService.start();
// now replacing the content of the users file with something that cannot be read
Files.write(tmp, ImmutableList.of("aldlfkjldjdflkjd"), Charsets.UTF_16);
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("Waited too long for the updated file to be picked up");
}
assertThat(store.usersCount(), is(0));
}
@Test
public void testParseFile() throws Exception {
Path path = Paths.get(getClass().getResource("users").toURI());
@ -60,63 +178,40 @@ public class FileUserPasswdStoreTests extends ElasticsearchTestCase {
}
@Test
public void testAutoReload() throws Exception {
ThreadPool threadPool = null;
ResourceWatcherService watcherService = null;
public void testParseFile_Empty() throws Exception {
File empty = newTempFile();
ESLogger log = ESLoggerFactory.getLogger("test");
log = spy(log);
ImmutableMap<String, char[]> users = FileUserPasswdStore.parseFile(empty.toPath(), log);
assertThat(users.isEmpty(), is(true));
verify(log, times(1)).warn(contains("no users found"), eq(empty.toPath().toAbsolutePath()));
}
@Test
public void testParseFile_WhenFileDoesNotExist() throws Exception {
File file = new File(randomAsciiOfLength(10));
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
Map<String, char[]> users = FileUserPasswdStore.parseFile(file.toPath(), logger);
assertThat(users, notNullValue());
assertThat(users.isEmpty(), is(true));
}
@Test
public void testParseFile_WhenCannotReadFile() throws Exception {
File file = newTempFile();
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
Files.write(file.toPath(), ImmutableList.of("aldlfkjldjdflkjd"), Charsets.UTF_16);
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
try {
Path users = Paths.get(getClass().getResource("users").toURI());
Path tmp = Files.createTempFile(null, null);
Files.copy(users, Files.newOutputStream(tmp));
Settings settings = ImmutableSettings.builder()
.put("watcher.interval.high", "2s")
.build();
Settings esusersSettings = ImmutableSettings.builder()
.put("files.users", tmp.toAbsolutePath())
.build();
Environment env = new Environment(settings);
RealmConfig config = new RealmConfig("esusers-test", esusersSettings, settings, env);
threadPool = new ThreadPool("test");
watcherService = new ResourceWatcherService(settings, threadPool);
final CountDownLatch latch = new CountDownLatch(1);
FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService, new RefreshListener() {
@Override
public void onRefresh() {
latch.countDown();
}
});
assertTrue(store.verifyPassword("bcrypt", SecuredStringTests.build("test123")));
watcherService.start();
try (BufferedWriter writer = Files.newBufferedWriter(tmp, Charsets.UTF_8, StandardOpenOption.APPEND)) {
writer.newLine();
writer.append("foobar:" + new String(Hasher.HTPASSWD.hash(SecuredStringTests.build("barfoo"))));
}
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("Waited too long for the updated file to be picked up");
}
assertTrue(store.verifyPassword("foobar", SecuredStringTests.build("barfoo")));
} finally {
if (watcherService != null) {
watcherService.stop();
}
if (threadPool != null) {
threadPool.shutdownNow();
}
FileUserPasswdStore.parseFile(file.toPath(), logger);
fail("expected a parse failure");
} catch (ShieldException se) {
this.logger.info("expected", se);
}
}
@Test
public void testThatInvalidLineDoesNotResultInLoggerNPE() throws Exception {
public void testParseFile_InvalidLineDoesNotResultInLoggerNPE() throws Exception {
File file = newTempFile();
com.google.common.io.Files.write("NotValidUsername=Password\nuser:pass".getBytes(org.elasticsearch.common.base.Charsets.UTF_8), file);
Map<String, char[]> users = FileUserPasswdStore.parseFile(file.toPath(), null);
@ -125,12 +220,17 @@ public class FileUserPasswdStoreTests extends ElasticsearchTestCase {
}
@Test
public void testParseEmptyFile() throws Exception {
File empty = newTempFile();
ESLogger log = ESLoggerFactory.getLogger("test");
log = spy(log);
Map<String, char[]> users = FileUserPasswdStore.parseFile(empty.toPath(), log);
verify(log, times(1)).warn(contains("no users found"), eq(empty.toPath().toAbsolutePath()));
public void testParseFileLenient_WhenCannotReadFile() throws Exception {
File file = newTempFile();
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
Files.write(file.toPath(), ImmutableList.of("aldlfkjldjdflkjd"), Charsets.UTF_16);
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
Map<String, char[]> users = FileUserPasswdStore.parseFileLenient(file.toPath(), logger);
assertThat(users, notNullValue());
assertThat(users.isEmpty(), is(true));
List<CapturingLogger.Msg> msgs = logger.output(CapturingLogger.Level.ERROR);
assertThat(msgs.size(), is(1));
assertThat(msgs.get(0).text, containsString("failed to parse users file"));
}
}

View File

@ -5,16 +5,22 @@
*/
package org.elasticsearch.shield.authc.esusers;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.audit.logfile.CapturingLogger;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.RefreshListener;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.BufferedWriter;
@ -23,18 +29,143 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.contains;
import static org.mockito.Mockito.*;
/**
*
*/
public class FileUserRolesStoreTests extends ElasticsearchTestCase {
private Settings settings;
private Environment env;
private ThreadPool threadPool;
@Before
public void init() {
settings = ImmutableSettings.builder()
.put("watcher.interval.high", "2s")
.build();
env = new Environment(settings);
threadPool = new ThreadPool("test");
}
@After
public void shutdown() {
threadPool.shutdownNow();
}
@Test
public void testStore_ConfiguredWithUnreadableFile() throws Exception {
File file = newTempFile();
List<String> lines = new ArrayList<>();
lines.add("aldlfkjldjdflkjd");
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
Files.write(file.toPath(), lines, Charsets.UTF_16);
Settings esusersSettings = ImmutableSettings.builder()
.put("files.users_roles", file.toPath().toAbsolutePath())
.build();
RealmConfig config = new RealmConfig("esusers-test", esusersSettings, settings, env);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
FileUserRolesStore store = new FileUserRolesStore(config, watcherService);
assertThat(store.entriesCount(), is(0));
}
@Test
public void testStore_AutoReload() throws Exception {
Path users = Paths.get(getClass().getResource("users_roles").toURI());
Path tmp = Files.createTempFile(null, null);
Files.copy(users, Files.newOutputStream(tmp));
Settings esusersSettings = ImmutableSettings.builder()
.put("files.users_roles", tmp.toAbsolutePath())
.build();
RealmConfig config = new RealmConfig("esusers-test", esusersSettings, settings, env);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
final CountDownLatch latch = new CountDownLatch(1);
FileUserRolesStore store = new FileUserRolesStore(config, watcherService, new RefreshListener() {
@Override
public void onRefresh() {
latch.countDown();
}
});
String[] roles = store.roles("user1");
assertThat(roles, notNullValue());
assertThat(roles.length, is(3));
assertThat(roles, arrayContaining("role1", "role2", "role3"));
assertThat(store.roles("user4"), equalTo(Strings.EMPTY_ARRAY));
watcherService.start();
try (BufferedWriter writer = Files.newBufferedWriter(tmp, Charsets.UTF_8, StandardOpenOption.APPEND)) {
writer.newLine();
writer.append("role4:user4\nrole5:user4\n");
}
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("Waited too long for the updated file to be picked up");
}
roles = store.roles("user4");
assertThat(roles, notNullValue());
assertThat(roles.length, is(2));
assertThat(roles, arrayContaining("role4", "role5"));
}
@Test
public void testStore_AutoReload_WithParseFailure() throws Exception {
Path users = Paths.get(getClass().getResource("users_roles").toURI());
Path tmp = Files.createTempFile(null, null);
Files.copy(users, Files.newOutputStream(tmp));
Settings esusersSettings = ImmutableSettings.builder()
.put("files.users_roles", tmp.toAbsolutePath())
.build();
RealmConfig config = new RealmConfig("esusers-test", esusersSettings, settings, env);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
final CountDownLatch latch = new CountDownLatch(1);
FileUserRolesStore store = new FileUserRolesStore(config, watcherService, new RefreshListener() {
@Override
public void onRefresh() {
latch.countDown();
}
});
String[] roles = store.roles("user1");
assertThat(roles, notNullValue());
assertThat(roles.length, is(3));
assertThat(roles, arrayContaining("role1", "role2", "role3"));
assertThat(store.roles("user4"), equalTo(Strings.EMPTY_ARRAY));
watcherService.start();
// now replacing the content of the users file with something that cannot be read
Files.write(tmp, ImmutableList.of("aldlfkjldjdflkjd"), Charsets.UTF_16);
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("Waited too long for the updated file to be picked up");
}
assertThat(store.entriesCount(), is(0));
}
@Test
public void testParseFile() throws Exception {
Path path = Paths.get(getClass().getResource("users_roles").toURI());
@ -53,69 +184,42 @@ public class FileUserRolesStoreTests extends ElasticsearchTestCase {
}
@Test
public void testAutoReload() throws Exception {
ThreadPool threadPool = null;
ResourceWatcherService watcherService = null;
public void testParseFile_Empty() throws Exception {
File empty = newTempFile();
ESLogger log = ESLoggerFactory.getLogger("test");
log = spy(log);
FileUserRolesStore.parseFile(empty.toPath(), log);
verify(log, times(1)).warn(contains("no entries found"), eq(empty.toPath().toAbsolutePath()));
}
@Test
public void testParseFile_WhenFileDoesNotExist() throws Exception {
File file = new File(randomAsciiOfLength(10));
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
Map<String, String[]> usersRoles = FileUserRolesStore.parseFile(file.toPath(), logger);
assertThat(usersRoles, notNullValue());
assertThat(usersRoles.isEmpty(), is(true));
}
@Test
public void testParseFile_WhenCannotReadFile() throws Exception {
File file = newTempFile();
List<String> lines = new ArrayList<>();
lines.add("aldlfkjldjdflkjd");
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
Files.write(file.toPath(), lines, Charsets.UTF_16);
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
try {
Path users = Paths.get(getClass().getResource("users_roles").toURI());
Path tmp = Files.createTempFile(null, null);
Files.copy(users, Files.newOutputStream(tmp));
Settings settings = ImmutableSettings.builder()
.put("watcher.interval.high", "2s")
.build();
Settings esusersSettings = ImmutableSettings.builder()
.put("files.users_roles", tmp.toAbsolutePath())
.build();
Environment env = new Environment(settings);
RealmConfig config = new RealmConfig("esusers-test", esusersSettings, settings, env);
threadPool = new ThreadPool("test");
watcherService = new ResourceWatcherService(settings, threadPool);
final CountDownLatch latch = new CountDownLatch(1);
FileUserRolesStore store = new FileUserRolesStore(config, watcherService, new RefreshListener() {
@Override
public void onRefresh() {
latch.countDown();
}
});
String[] roles = store.roles("user1");
assertThat(roles, notNullValue());
assertThat(roles.length, is(3));
assertThat(roles, arrayContaining("role1", "role2", "role3"));
assertThat(store.roles("user4"), equalTo(Strings.EMPTY_ARRAY));
watcherService.start();
try (BufferedWriter writer = Files.newBufferedWriter(tmp, Charsets.UTF_8, StandardOpenOption.APPEND)) {
writer.newLine();
writer.append("role4:user4\nrole5:user4\n");
}
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("Waited too long for the updated file to be picked up");
}
roles = store.roles("user4");
assertThat(roles, notNullValue());
assertThat(roles.length, is(2));
assertThat(roles, arrayContaining("role4", "role5"));
} finally {
if (watcherService != null) {
watcherService.stop();
}
if (threadPool != null) {
threadPool.shutdownNow();
}
FileUserRolesStore.parseFile(file.toPath(), logger);
fail("expected a parse failure");
} catch (Throwable t) {
this.logger.info("expected", t);
}
}
@Test
public void testThatEmptyRolesDoesNotCauseNPE() throws Exception {
public void testParseFile_EmptyRolesDoesNotCauseNPE() throws Exception {
ThreadPool threadPool = null;
try {
threadPool = new ThreadPool("test");
@ -130,7 +234,7 @@ public class FileUserRolesStoreTests extends ElasticsearchTestCase {
.build();
Environment env = new Environment(settings);
RealmConfig config = new RealmConfig("esusers-test", ImmutableSettings.EMPTY, settings, env);
RealmConfig config = new RealmConfig("esusers-test", esusersSettings, settings, env);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
FileUserRolesStore store = new FileUserRolesStore(config, watcherService);
assertThat(store.roles("user"), equalTo(Strings.EMPTY_ARRAY));
@ -142,24 +246,41 @@ public class FileUserRolesStoreTests extends ElasticsearchTestCase {
}
@Test
public void testThatEmptyFileIsParsed() throws Exception {
public void testParseFile_EmptyFileIsParsed() throws Exception {
assertInvalidInputIsSilentlyIgnored("");
assertInvalidInputIsSilentlyIgnored("#");
}
@Test
public void testThatEmptyRoleNameDoesNotThrowException() throws Exception {
public void testParseFile_EmptyRoleNameDoesNotThrowException() throws Exception {
assertInvalidInputIsSilentlyIgnored(":user1,user2");
assertInvalidInputIsSilentlyIgnored(" :user1,user2");
}
@Test
public void testThatEmptyRoleDoesNotThrowException() throws Exception {
public void testParseFile_EmptyRoleDoesNotThrowException() throws Exception {
assertInvalidInputIsSilentlyIgnored("role:");
assertInvalidInputIsSilentlyIgnored("role: ");
assertInvalidInputIsSilentlyIgnored("role: , ");
}
@Test
public void testParseFileLenient_WhenCannotReadFile() throws Exception {
File file = newTempFile();
List<String> lines = new ArrayList<>();
lines.add("aldlfkjldjdflkjd");
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
Files.write(file.toPath(), lines, Charsets.UTF_16);
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
Map<String, String[]> usersRoles = FileUserRolesStore.parseFileLenient(file.toPath(), logger);
assertThat(usersRoles, notNullValue());
assertThat(usersRoles.isEmpty(), is(true));
List<CapturingLogger.Msg> msgs = logger.output(CapturingLogger.Level.ERROR);
assertThat(msgs.size(), is(1));
assertThat(msgs.get(0).text, containsString("failed to parse users_roles file"));
}
private File writeUsersRoles(String input) throws Exception {
File file = newTempFile();
com.google.common.io.Files.write(input.getBytes(Charsets.UTF_8), file);

View File

@ -9,23 +9,20 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.ldap.AbstractGroupToRoleMapper;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.shield.authc.support.ldap.AbstractGroupToRoleMapperTests;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Set;
import static org.hamcrest.Matchers.hasItems;
public class LdapGroupToRoleMapperTest extends ElasticsearchTestCase {
public class LdapGroupToRoleMapperTests extends AbstractGroupToRoleMapperTests {
private final String tonyStarkDN = "cn=tstark,ou=marvel,o=superheros";
private final String[] starkGroupDns = new String[] {
//groups can be named by different attributes, depending on the directory,
//we don't care what it is named by
@ -36,47 +33,43 @@ public class LdapGroupToRoleMapperTest extends ElasticsearchTestCase {
"gid = playboy , dc = example , dc = com",
"groupid=philanthropist,ou=groups,dc=unitedway,dc=org"
};
private final String roleShield = "shield";
private final String roleAvenger = "avenger";
private ThreadPool threadPool;
@Before
public void init() {
threadPool = new ThreadPool("test");
@Override
protected AbstractGroupToRoleMapper createMapper(Path file, ResourceWatcherService watcherService) {
Settings ldapSettings = ImmutableSettings.builder()
.put("files.role_mapping", file.toAbsolutePath())
.build();
RealmConfig config = new RealmConfig("ldap-group-mapper-test", ldapSettings, settings, env);
return new LdapGroupToRoleMapper(config, watcherService);
}
@After
public void shutdown() {
threadPool.shutdownNow();
}
@Test
public void testYaml() throws IOException {
File file = this.getResource("../support/ldap/role_mapping.yml");
Settings settings = ImmutableSettings.settingsBuilder()
Settings ldapSettings = ImmutableSettings.settingsBuilder()
.put(LdapGroupToRoleMapper.ROLE_MAPPING_FILE_SETTING, file.getCanonicalPath())
.build();
RealmConfig config = new RealmConfig("ldap1", settings);
RealmConfig config = new RealmConfig("ldap1", ldapSettings);
AbstractGroupToRoleMapper mapper = new LdapGroupToRoleMapper(config, new ResourceWatcherService(settings, threadPool));
Set<String> roles = mapper.mapRoles( Arrays.asList(starkGroupDns) );
//verify
assertThat(roles, hasItems(roleShield, roleAvenger));
assertThat(roles, hasItems("shield", "avenger"));
}
@Test
public void testRelativeDN() {
Settings settings = ImmutableSettings.builder()
Settings ldapSettings = ImmutableSettings.builder()
.put(AbstractGroupToRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true)
.build();
RealmConfig config = new RealmConfig("ldap1", settings);
RealmConfig config = new RealmConfig("ldap1", ldapSettings);
AbstractGroupToRoleMapper mapper = new LdapGroupToRoleMapper(config, new ResourceWatcherService(settings, threadPool));
Set<String> roles = mapper.mapRoles(Arrays.asList(starkGroupDns));
assertThat(roles, hasItems("genius", "billionaire", "playboy", "philanthropist", "shield", "avengers"));
}
}

View File

@ -0,0 +1,216 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.support.ldap;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.audit.logfile.CapturingLogger;
import org.elasticsearch.shield.authc.ldap.LdapGroupToRoleMapper;
import org.elasticsearch.shield.authc.support.RefreshListener;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.naming.ldap.LdapName;
import java.io.BufferedWriter;
import java.io.File;
import java.nio.file.*;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.*;
/**
*
*/
public abstract class AbstractGroupToRoleMapperTests extends ElasticsearchTestCase {
protected Settings settings;
protected Environment env;
protected ThreadPool threadPool;
@Before
public void init() {
settings = ImmutableSettings.builder()
.put("watcher.interval.high", "2s")
.build();
env = new Environment(settings);
threadPool = new ThreadPool("test");
}
@After
public void shutdown() {
threadPool.shutdownNow();
}
protected abstract AbstractGroupToRoleMapper createMapper(Path file, ResourceWatcherService watcherService);
@Test
public void testMapper_ConfiguredWithUnreadableFile() throws Exception {
Path file = newTempFile().toPath();
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
Files.write(file, ImmutableList.of("aldlfkjldjdflkjd"), Charsets.UTF_16);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
AbstractGroupToRoleMapper mapper = createMapper(file, watcherService);
assertThat(mapper.mappingsCount(), is(0));
}
@Test
public void testMapper_AutoReload() throws Exception {
Path roleMappingFile = Paths.get(AbstractGroupToRoleMapperTests.class.getResource("role_mapping.yml").toURI());
Path file = Files.createTempFile(null, ".yml");
Files.copy(roleMappingFile, file, StandardCopyOption.REPLACE_EXISTING);
final CountDownLatch latch = new CountDownLatch(1);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
AbstractGroupToRoleMapper mapper = createMapper(file, watcherService);
mapper.addListener(new RefreshListener() {
@Override
public void onRefresh() {
latch.countDown();
}
});
Set<String> roles = mapper.mapRoles(ImmutableList.of("cn=shield,ou=marvel,o=superheros"));
assertThat(roles, notNullValue());
assertThat(roles.size(), is(1));
assertThat(roles, contains("shield"));
watcherService.start();
try (BufferedWriter writer = Files.newBufferedWriter(file, Charsets.UTF_8, StandardOpenOption.APPEND)) {
writer.newLine();
writer.append("fantastic_four:\n")
.append(" - \"cn=fantastic_four,ou=marvel,o=superheros\"");
}
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("Waited too long for the updated file to be picked up");
}
roles = mapper.mapRoles(ImmutableList.of("cn=fantastic_four,ou=marvel,o=superheros"));
assertThat(roles, notNullValue());
assertThat(roles.size(), is(1));
assertThat(roles, contains("fantastic_four"));
}
@Test
public void testMapper_AutoReload_WithParseFailures() throws Exception {
Path roleMappingFile = Paths.get(AbstractGroupToRoleMapperTests.class.getResource("role_mapping.yml").toURI());
Path file = Files.createTempFile(null, ".yml");
Files.copy(roleMappingFile, file, StandardCopyOption.REPLACE_EXISTING);
final CountDownLatch latch = new CountDownLatch(1);
ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
AbstractGroupToRoleMapper mapper = createMapper(file, watcherService);
mapper.addListener(new RefreshListener() {
@Override
public void onRefresh() {
latch.countDown();
}
});
Set<String> roles = mapper.mapRoles(ImmutableList.of("cn=shield,ou=marvel,o=superheros"));
assertThat(roles, notNullValue());
assertThat(roles.size(), is(1));
assertThat(roles, contains("shield"));
watcherService.start();
// now replacing the content of the users file with something that cannot be read
Files.write(file, ImmutableList.of("aldlfkjldjdflkjd"), Charsets.UTF_16);
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("Waited too long for the updated file to be picked up");
}
assertThat(mapper.mappingsCount(), is(0));
}
@Test
public void testParseFile() throws Exception {
Path file = Paths.get(AbstractGroupToRoleMapperTests.class.getResource("role_mapping.yml").toURI());
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
ImmutableMap<LdapName, Set<String>> mappings = LdapGroupToRoleMapper.parseFile(file, logger, "_type", "_name");
assertThat(mappings, notNullValue());
assertThat(mappings.size(), is(2));
LdapName ldapName = new LdapName("cn=avengers,ou=marvel,o=superheros");
assertThat(mappings, hasKey(ldapName));
Set<String> roles = mappings.get(ldapName);
assertThat(roles, notNullValue());
assertThat(roles, hasSize(2));
assertThat(roles, containsInAnyOrder("shield", "avenger"));
ldapName = new LdapName("cn=shield,ou=marvel,o=superheros");
assertThat(mappings, hasKey(ldapName));
roles = mappings.get(ldapName);
assertThat(roles, notNullValue());
assertThat(roles, hasSize(1));
assertThat(roles, containsInAnyOrder("shield"));
}
@Test
public void testParseFile_Empty() throws Exception {
Path file = newTempFile().toPath();
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
ImmutableMap<LdapName, Set<String>> mappings = LdapGroupToRoleMapper.parseFile(file, logger, "_type", "_name");
assertThat(mappings, notNullValue());
assertThat(mappings.isEmpty(), is(true));
List<CapturingLogger.Msg> msgs = logger.output(CapturingLogger.Level.WARN);
assertThat(msgs.size(), is(1));
assertThat(msgs.get(0).text, containsString("no mappings found"));
}
@Test
public void testParseFile_WhenFileDoesNotExist() throws Exception {
Path file = new File(randomAsciiOfLength(10)).toPath();
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
ImmutableMap<LdapName, Set<String>> mappings = LdapGroupToRoleMapper.parseFile(file, logger, "_type", "_name");
assertThat(mappings, notNullValue());
assertThat(mappings.isEmpty(), is(true));
}
@Test
public void testParseFile_WhenCannotReadFile() throws Exception {
Path file = newTempFile().toPath();
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
Files.write(file, ImmutableList.of("aldlfkjldjdflkjd"), Charsets.UTF_16);
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
try {
LdapGroupToRoleMapper.parseFile(file, logger, "_type", "_name");
fail("expected a parse failure");
} catch (Exception e) {
this.logger.info("expected", e);
}
}
@Test
public void testParseFileLenient_WhenCannotReadFile() throws Exception {
Path file = newTempFile().toPath();
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
Files.write(file, ImmutableList.of("aldlfkjldjdflkjd"), Charsets.UTF_16);
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
ImmutableMap<LdapName, Set<String>> mappings = LdapGroupToRoleMapper.parseFileLenient(file, logger, "_type", "_name");
assertThat(mappings, notNullValue());
assertThat(mappings.isEmpty(), is(true));
List<CapturingLogger.Msg> msgs = logger.output(CapturingLogger.Level.ERROR);
assertThat(msgs.size(), is(1));
assertThat(msgs.get(0).text, containsString("failed to parse role mappings file"));
}
}