CmdTool: Added options for editing roles and listing users/roles

Two new commands to the command line tool have been added

* esusers list: Allows to list all users with their roles or only a single one
* esusers roles: Allows to add or remove roles

Roles have been configured as to only consist of lowercase alphanumeric characters.

Original commit: elastic/x-pack-elasticsearch@6fcb4c56e4
This commit is contained in:
Alexander Reelsen 2014-08-08 17:36:29 +02:00
parent 75cf637fed
commit 3ee4d527f3
4 changed files with 527 additions and 129 deletions

View File

@ -6,10 +6,15 @@
package org.elasticsearch.shield.authc.esusers.tool;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.base.Joiner;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.cli.commons.CommandLine;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.collect.Maps;
import org.elasticsearch.common.collect.ObjectArrays;
import org.elasticsearch.common.collect.Sets;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
@ -17,10 +22,8 @@ import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
import org.elasticsearch.shield.authc.support.Hasher;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.*;
import java.util.regex.Pattern;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
@ -31,7 +34,7 @@ import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
public class ESUsersTool extends CliTool {
private static final CliToolConfig CONFIG = CliToolConfig.config("esusers", ESUsersTool.class)
.cmds(Useradd.CMD, Userdel.CMD, Passwd.CMD)
.cmds(Useradd.CMD, Userdel.CMD, Passwd.CMD, Roles.CMD, ListUsersAndRoles.CMD)
.build();
public static void main(String[] args) {
@ -49,9 +52,16 @@ public class ESUsersTool extends CliTool {
@Override
protected Command parse(String cmdName, CommandLine cli) throws Exception {
switch (cmdName.toLowerCase(Locale.ROOT)) {
case Useradd.NAME: return Useradd.parse(terminal, cli);
case Userdel.NAME: return Userdel.parse(terminal, cli);
case Passwd.NAME: return Passwd.parse(terminal, cli);
case Useradd.NAME:
return Useradd.parse(terminal, cli);
case Userdel.NAME:
return Userdel.parse(terminal, cli);
case Passwd.NAME:
return Passwd.parse(terminal, cli);
case ListUsersAndRoles.NAME:
return ListUsersAndRoles.parse(terminal, cli);
case Roles.NAME:
return Roles.parse(terminal, cli);
default:
assert false : "should never get here, if the user enters an unknown command, an error message should be shown before parse is called";
return null;
@ -112,7 +122,7 @@ public class ESUsersTool extends CliTool {
users = new HashMap<>();
}
if (users.containsKey(username)) {
terminal.println("User [{}] already exists", username);
terminal.println("User [%s] already exists", username);
return ExitStatus.CODE_ERROR;
}
Hasher hasher = Hasher.HTPASSWD;
@ -229,7 +239,7 @@ public class ESUsersTool extends CliTool {
users = new HashMap<>();
}
if (!users.containsKey(username)) {
terminal.println("User [{}] doesn't exist", username);
terminal.println("User [%s] doesn't exist", username);
return ExitStatus.NO_USER;
}
Hasher hasher = Hasher.HTPASSWD;
@ -238,4 +248,138 @@ public class ESUsersTool extends CliTool {
return ExitStatus.OK;
}
}
}
static class Roles extends CliTool.Command {
private static final String NAME = "roles";
private static final CliToolConfig.Cmd CMD = cmd(NAME, Roles.class)
.options(
option("a", "add").hasArg(true).required(false),
option("r", "remove").hasArg(true).required(false))
.build();
public static Command parse(Terminal terminal, CommandLine cli) {
if (cli.getArgs().length == 0) {
return exitCmd(ExitStatus.USAGE, terminal, "username is missing");
}
String username = cli.getArgs()[0];
String addRolesCsv = cli.getOptionValue("add");
String[] addRoles = (addRolesCsv != null) ? addRolesCsv.split(",") : Strings.EMPTY_ARRAY;
String removeRolesCsv = cli.getOptionValue("remove");
String[] removeRoles = (removeRolesCsv != null) ? removeRolesCsv.split(",") : Strings.EMPTY_ARRAY;
return new Roles(terminal, username, addRoles, removeRoles);
}
public static final Pattern ROLE_PATTERN = Pattern.compile("[\\w@-]+");
final String username;
final String[] addRoles;
final String[] removeRoles;
public Roles(Terminal terminal, String username, String[] addRoles, String[] removeRoles) {
super(terminal);
this.username = username;
this.addRoles = addRoles;
this.removeRoles = removeRoles;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
// check if just need to return data as no write operation happens
// Nothing to add, just list the data for a username
boolean readOnlyUserListing = removeRoles.length == 0 && addRoles.length == 0;
if (readOnlyUserListing) {
return new ListUsersAndRoles(terminal, username).execute(settings, env);
}
// check for roles if they match
String[] allRoles = ObjectArrays.concat(addRoles, removeRoles, String.class);
for (String role : allRoles) {
if (!ROLE_PATTERN.matcher(role).matches()) {
terminal.println("Role name [%s] is not valid. Please use lowercase and numbers only", role);
return ExitStatus.DATA_ERROR;
}
}
Path file = FileUserRolesStore.resolveFile(settings, env);
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(file, null);
if (!userRoles.containsKey(username)) {
terminal.println("User [%s] doesn't exist", username);
return ExitStatus.NO_USER;
}
List<String> roles = Lists.newArrayList(userRoles.get(username));;
roles.addAll(Arrays.asList(addRoles));
roles.removeAll(Arrays.asList(removeRoles));
Map<String, String[]> userRolesToWrite = Maps.newHashMapWithExpectedSize(userRoles.size());
userRolesToWrite.putAll(userRoles);
userRolesToWrite.put(username, Sets.newLinkedHashSet(roles).toArray(new String[]{}));
FileUserRolesStore.writeFile(userRolesToWrite, file);
return ExitStatus.OK;
}
}
static class ListUsersAndRoles extends CliTool.Command {
private static final String NAME = "list";
private static final CliToolConfig.Cmd CMD = cmd(NAME, Useradd.class).build();
public static Command parse(Terminal terminal, CommandLine cli) {
if (cli.getArgs().length == 0) {
return exitCmd(ExitStatus.USAGE, terminal, "username is missing");
}
String username = cli.getArgs()[0];
return new ListUsersAndRoles(terminal, username);
}
String username;
public ListUsersAndRoles(Terminal terminal, String username) {
super(terminal);
this.username = username;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
Path userRolesFilePath = FileUserRolesStore.resolveFile(settings, env);
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null);
Path userFilePath = FileUserPasswdStore.resolveFile(settings, env);
Set<String> users = FileUserPasswdStore.parseFile(userFilePath, null).keySet();
if (username != null) {
if (!users.contains(username)) {
terminal.println("User [%s] doesn't exist", username);
return ExitStatus.NO_USER;
}
if (userRoles.containsKey(username)) {
terminal.println("%-15s: %s", username, Joiner.on(",").useForNull("-").join(userRoles.get(username)));
} else {
terminal.println("%-15s: -", username);
}
} else {
for (Map.Entry<String, String[]> entry : userRoles.entrySet()) {
terminal.println("%-15s: %s", entry.getKey(), Joiner.on(",").join(entry.getValue()));
}
// list users without roles
Set<String> usersWithoutRoles = Sets.newHashSet(users);
if (usersWithoutRoles.removeAll(userRoles.keySet())) {
for (String user : usersWithoutRoles) {
terminal.println("%-15s: -", user);
}
}
}
return ExitStatus.OK;
}
}
}

View File

@ -0,0 +1,17 @@
NAME
list - List existing users and their corresponding roles
SYNOPSIS
esusers list <username>
DESCRIPTION
The list command allows to list all existing users and their
corresponding roles. Alternatively you can also list just a
single user.
OPTIONS
-h,--help Shows this message

View File

@ -0,0 +1,21 @@
NAME
roles - Edit roles of an existing user
SYNOPSIS
esusers roles <username> [-a roles] [-r roles]
DESCRIPTION
The roles command allows to edit roles for an existing user.
corresponding roles. Alternatively you can also list just a
single users roles, if you do not specify the -a or the -r parameter.
OPTIONS
-h,--help Shows this message
-a,--add <roles> Adds supplied roles for the specified user
-r,--remove <roles> Adds supplied roles for the specified user

View File

@ -5,20 +5,27 @@
*/
package org.elasticsearch.shield.authc.esusers.tool;
import com.google.common.io.Files;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
import org.elasticsearch.shield.authc.support.Hasher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.Matchers.*;
@ -28,6 +35,9 @@ import static org.hamcrest.Matchers.*;
*/
public class ESUsersToolTests extends CliToolTestCase {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void testUseradd_Parse_AllOptions() throws Exception {
ESUsersTool tool = new ESUsersTool();
@ -67,22 +77,21 @@ public class ESUsersToolTests extends CliToolTestCase {
@Test
public void testUseradd_Cmd_Create() throws Exception {
Path dir = Files.createTempDirectory(null);
Path users = dir.resolve("users");
Path usersRoles = dir.resolve("users_roles");
File tmpFolder = temporaryFolder.newFolder();
File userFile = new File(tmpFolder, "users");
File userRolesFile = new File(tmpFolder, "users_roles");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
.put("shield.authc.esusers.files.users", userFile)
.put("shield.authc.esusers.files.users_roles", userRolesFile)
.build();
Environment env = new Environment(settings);
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new TerminalMock(), "user1", "changeme".toCharArray(), "r1", "r2");
CliTool.ExitStatus status = cmd.execute(settings, env);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(Files.exists(users), is(true));
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
assertFileExists(userFile);
List<String> lines = Files.readLines(userFile, Charsets.UTF_8);
assertThat(lines.size(), is(1));
// we can't just hash again and compare the lines, as every time we hash a new salt is generated
// instead we'll just verify the generated hash against the correct password.
@ -91,8 +100,8 @@ public class ESUsersToolTests extends CliToolTestCase {
String hash = line.substring("user1:".length());
assertThat(Hasher.HTPASSWD.verify("changeme".toCharArray(), hash.toCharArray()), is(true));
assertThat(Files.exists(usersRoles), is(true));
lines = Files.readAllLines(usersRoles, Charsets.UTF_8);
assertFileExists(userRolesFile);
lines = Files.readLines(userRolesFile, Charsets.UTF_8);
assertThat(lines.size(), is(1));
line = lines.get(0);
assertThat(line, equalTo("user1:r1,r2"));
@ -100,31 +109,20 @@ public class ESUsersToolTests extends CliToolTestCase {
@Test
public void testUseradd_Cmd_Append() throws Exception {
Path users = Files.createTempFile(null, null);
Path usersRoles = Files.createTempFile(null, null);
File userFile = writeFile("user2:hash2");
File userRolesFile = writeFile("user2:r3,r4");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
.put("shield.authc.esusers.files.users", userFile)
.put("shield.authc.esusers.files.users_roles", userRolesFile)
.build();
Environment env = new Environment(settings);
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
writer.write("user2:hash2");
writer.flush();
}
try (BufferedWriter writer = Files.newBufferedWriter(usersRoles, Charsets.UTF_8)) {
writer.write("user2:r3,r4");
writer.flush();
}
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new TerminalMock(), "user1", "changeme".toCharArray(), "r1", "r2");
CliTool.ExitStatus status = cmd.execute(settings, env);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(Files.exists(users), is(true));
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
assertFileExists(userFile);
List<String> lines = Files.readLines(userFile, Charsets.UTF_8);
assertThat(lines.size(), is(2));
assertThat(lines.get(0), equalTo("user2:hash2"));
// we can't just hash again and compare the lines, as every time we hash a new salt is generated
@ -134,8 +132,8 @@ public class ESUsersToolTests extends CliToolTestCase {
String hash = line.substring("user1:".length());
assertThat(Hasher.HTPASSWD.verify("changeme".toCharArray(), hash.toCharArray()), is(true));
assertThat(Files.exists(usersRoles), is(true));
lines = Files.readAllLines(usersRoles, Charsets.UTF_8);
assertFileExists(userRolesFile);
lines = Files.readLines(userRolesFile, Charsets.UTF_8);
assertThat(lines.size(), is(2));
assertThat(lines.get(0), equalTo("user2:r3,r4"));
line = lines.get(1);
@ -144,22 +142,16 @@ public class ESUsersToolTests extends CliToolTestCase {
@Test
public void testUseradd_Cmd_Append_UserAlreadyExists() throws Exception {
Path users = Files.createTempFile(null, null);
Path usersRoles = Files.createTempFile(null, null);
File userFile = writeFile("user1:hash1");
File userRolesFile = temporaryFolder.newFile();
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
.put("shield.authc.esusers.files.users", userFile)
.put("shield.authc.esusers.files.users_roles", userRolesFile)
.build();
Environment env = new Environment(settings);
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
writer.write("user1:hash1");
writer.flush();
}
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new TerminalMock(), "user1", "changeme".toCharArray(), "r1", "r2");
CliTool.ExitStatus status = cmd.execute(settings, env);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.CODE_ERROR));
}
@ -183,90 +175,67 @@ public class ESUsersToolTests extends CliToolTestCase {
@Test
public void testUserdel_Cmd() throws Exception {
Path users = Files.createTempFile(null, null);
Path usersRoles = Files.createTempFile(null, null);
File userFile = writeFile("user1:hash2");
File userRolesFile = writeFile("user1:r3,r4");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
.put("shield.authc.esusers.files.users", userFile)
.put("shield.authc.esusers.files.users_roles", userRolesFile)
.build();
Environment env = new Environment(settings);
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
writer.write("user1:hash2");
writer.flush();
}
try (BufferedWriter writer = Files.newBufferedWriter(usersRoles, Charsets.UTF_8)) {
writer.write("user1:r3,r4");
writer.flush();
}
ESUsersTool.Userdel cmd = new ESUsersTool.Userdel(new TerminalMock(), "user1");
CliTool.ExitStatus status = cmd.execute(settings, env);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(Files.exists(users), is(true));
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
assertFileExists(userFile);
List<String> lines = Files.readLines(userFile, Charsets.UTF_8);
assertThat(lines.size(), is(0));
assertThat(Files.exists(usersRoles), is(true));
lines = Files.readAllLines(usersRoles, Charsets.UTF_8);
assertFileExists(userRolesFile);
lines = Files.readLines(userRolesFile, Charsets.UTF_8);
assertThat(lines.size(), is(0));
}
@Test
public void testUserdel_Cmd_MissingUser() throws Exception {
Path users = Files.createTempFile(null, null);
Path usersRoles = Files.createTempFile(null, null);
File userFile = writeFile("user1:hash2");
File userRolesFile = writeFile("user1:r3,r4");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
.put("shield.authc.esusers.files.users", userFile)
.put("shield.authc.esusers.files.users_roles", userRolesFile)
.build();
Environment env = new Environment(settings);
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
writer.write("user1:hash2");
writer.flush();
}
try (BufferedWriter writer = Files.newBufferedWriter(usersRoles, Charsets.UTF_8)) {
writer.write("user1:r3,r4");
writer.flush();
}
ESUsersTool.Userdel cmd = new ESUsersTool.Userdel(new TerminalMock(), "user2");
CliTool.ExitStatus status = cmd.execute(settings, env);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(Files.exists(users), is(true));
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
assertFileExists(userFile);
List<String> lines = Files.readLines(userFile, Charsets.UTF_8);
assertThat(lines.size(), is(1));
assertThat(Files.exists(usersRoles), is(true));
lines = Files.readAllLines(usersRoles, Charsets.UTF_8);
assertFileExists(userRolesFile);
lines = Files.readLines(userRolesFile, Charsets.UTF_8);
assertThat(lines.size(), is(1));
}
@Test
public void testUserdel_Cmd_MissingFiles() throws Exception {
Path dir = Files.createTempDirectory(null);
Path users = dir.resolve("users");
Path usersRoles = dir.resolve("users_roles");
File dir = temporaryFolder.newFolder();
File userFile = new File(dir, "users");
File userRolesFile = new File(dir, "users_roles");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
.put("shield.authc.esusers.files.users", userFile)
.put("shield.authc.esusers.files.users_roles", userRolesFile)
.build();
Environment env = new Environment(settings);
ESUsersTool.Userdel cmd = new ESUsersTool.Userdel(new TerminalMock(), "user2");
CliTool.ExitStatus status = cmd.execute(settings, env);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(Files.exists(users), is(false));
assertThat(Files.exists(usersRoles), is(false));
assertThat(userFile.exists(), is(false));
assertThat(userRolesFile.exists(), is(false));
}
@Test
@ -309,22 +278,16 @@ public class ESUsersToolTests extends CliToolTestCase {
@Test
public void testPasswd_Cmd() throws Exception {
Path users = Files.createTempFile(null, null);
File userFile = writeFile("user1:hash2");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
.put("shield.authc.esusers.files.users", userFile)
.build();
Environment env = new Environment(settings);
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
writer.write("user1:hash2");
writer.flush();
}
ESUsersTool.Passwd cmd = new ESUsersTool.Passwd(new TerminalMock(), "user1", "changeme".toCharArray());
CliTool.ExitStatus status = cmd.execute(settings, env);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
List<String> lines = Files.readLines(userFile, Charsets.UTF_8);
assertThat(lines.size(), is(1));
// we can't just hash again and compare the lines, as every time we hash a new salt is generated
// instead we'll just verify the generated hash against the correct password.
@ -336,34 +299,287 @@ public class ESUsersToolTests extends CliToolTestCase {
@Test
public void testPasswd_Cmd_UnknownUser() throws Exception {
Path users = Files.createTempFile(null, null);
File userFile = writeFile("user1:hash2");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
.put("shield.authc.esusers.files.users", userFile)
.build();
Environment env = new Environment(settings);
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
writer.write("user1:hash2");
writer.flush();
}
ESUsersTool.Passwd cmd = new ESUsersTool.Passwd(new TerminalMock(), "user2", "changeme".toCharArray());
CliTool.ExitStatus status = cmd.execute(settings, env);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.NO_USER));
}
@Test
public void testPasswd_Cmd_MissingFiles() throws Exception {
Path dir = Files.createTempDirectory(null);
Path users = dir.resolve("users");
File userFile = temporaryFolder.newFile();
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
.put("shield.authc.esusers.files.users", userFile)
.build();
Environment env = new Environment(settings);
ESUsersTool.Passwd cmd = new ESUsersTool.Passwd(new TerminalMock(), "user2", "changeme".toCharArray());
CliTool.ExitStatus status = cmd.execute(settings, env);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.NO_USER));
}
@Test
public void testRoles_Parse_AllOptions() throws Exception {
ESUsersTool tool = new ESUsersTool();
CliTool.Command command = tool.parse("roles", args("someuser -a test1,test2,test3 -r test4,test5,test6"));
assertThat(command, instanceOf(ESUsersTool.Roles.class));
ESUsersTool.Roles rolesCommand = (ESUsersTool.Roles) command;
assertThat(rolesCommand.username, is("someuser"));
assertThat(rolesCommand.addRoles, arrayContaining("test1", "test2", "test3"));
assertThat(rolesCommand.removeRoles, arrayContaining("test4", "test5", "test6"));
}
@Test
public void testRoles_Cmd_validatingRoleNames() throws Exception {
ESUsersTool tool = new ESUsersTool();
File usersFile = writeFile("admin:hash");
File usersRoleFile = writeFile("admin: admin\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", usersFile)
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.build();
// invalid role names
assertThat(execute(tool.parse("roles", args("admin -a r0le!")), settings), is(CliTool.ExitStatus.DATA_ERROR));
assertThat(execute(tool.parse("roles", args("admin -a role%")), settings), is(CliTool.ExitStatus.DATA_ERROR));
assertThat(execute(tool.parse("roles", args("admin -a role:")), settings), is(CliTool.ExitStatus.DATA_ERROR));
assertThat(execute(tool.parse("roles", args("admin -a role>")), settings), is(CliTool.ExitStatus.DATA_ERROR));
// valid role names
assertThat(execute(tool.parse("roles", args("admin -a test01")), settings), is(CliTool.ExitStatus.OK));
assertThat(execute(tool.parse("roles", args("admin -a @role")), settings), is(CliTool.ExitStatus.OK));
assertThat(execute(tool.parse("roles", args("admin -a _role")), settings), is(CliTool.ExitStatus.OK));
assertThat(execute(tool.parse("roles", args("admin -a -role")), settings), is(CliTool.ExitStatus.OK));
assertThat(execute(tool.parse("roles", args("admin -a -Role")), settings), is(CliTool.ExitStatus.OK));
assertThat(execute(tool.parse("roles", args("admin -a role0")), settings), is(CliTool.ExitStatus.OK));
}
@Test
public void testRoles_Cmd_addingRoleWorks() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser:user\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.build();
ESUsersTool.Roles cmd = new ESUsersTool.Roles(new TerminalMock(), "user", new String[]{"foo"}, Strings.EMPTY_ARRAY);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(usersRoleFile.toPath(), logger);
assertThat(userRoles.keySet(), hasSize(2));
assertThat(userRoles.keySet(), hasItems("admin", "user"));
assertThat(userRoles.get("admin"), arrayContaining("admin"));
assertThat(userRoles.get("user"), arrayContaining("user", "foo"));
}
@Test
public void testRoles_Cmd_removingRoleWorks() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser:user,foo,bar\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.build();
ESUsersTool.Roles cmd = new ESUsersTool.Roles(new TerminalMock(), "user", Strings.EMPTY_ARRAY, new String[]{"foo"});
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(usersRoleFile.toPath(), logger);
assertThat(userRoles.keySet(), hasSize(2));
assertThat(userRoles.keySet(), hasItems("admin", "user"));
assertThat(userRoles.get("admin"), arrayContaining("admin"));
assertThat(userRoles.get("user"), arrayContaining("user", "bar"));
}
@Test
public void testRoles_Cmd_addingAndRemovingRoleWorks() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser:user,foo,bar\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.build();
ESUsersTool.Roles cmd = new ESUsersTool.Roles(new TerminalMock(), "user", new String[]{"newrole"}, new String[]{"foo"});
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(usersRoleFile.toPath(), logger);
assertThat(userRoles.keySet(), hasSize(2));
assertThat(userRoles.keySet(), hasItems("admin", "user"));
assertThat(userRoles.get("admin"), arrayContaining("admin"));
assertThat(userRoles.get("user"), arrayContaining("user", "bar", "newrole"));
}
@Test
public void testRoles_Cmd_userNotFound() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser:user,foo,bar\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.build();
ESUsersTool.Roles cmd = new ESUsersTool.Roles(new TerminalMock(), "does-not-exist", Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.NO_USER));
}
@Test
public void testRoles_Cmd_testNotAddingOrRemovingRolesShowsListingOfRoles() throws Exception {
File usersFile = writeFile("admin:hash\nuser:hash");
File usersRoleFile = writeFile("admin: admin\nuser:user,foo,bar\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", usersFile)
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.build();
LoggingTerminal catchTerminalOutput = new LoggingTerminal();
ESUsersTool.Roles cmd = new ESUsersTool.Roles(catchTerminalOutput, "user", Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(allOf(containsString("user"), containsString("user,foo,bar"))));
}
@Test
public void testListUsersAndRoles_Cmd_parsingWorks() throws Exception {
ESUsersTool tool = new ESUsersTool();
CliTool.Command command = tool.parse("list", args("someuser"));
assertThat(command, instanceOf(ESUsersTool.ListUsersAndRoles.class));
ESUsersTool.ListUsersAndRoles listUsersAndRolesCommand = (ESUsersTool.ListUsersAndRoles) command;
assertThat(listUsersAndRolesCommand.username, is("someuser"));
}
@Test
public void testListUsersAndRoles_Cmd_listAllUsers() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.build();
LoggingTerminal catchTerminalOutput = new LoggingTerminal();
ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, null);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(catchTerminalOutput.getTerminalOutput(), hasSize(greaterThanOrEqualTo(2)));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(containsString("admin")));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(allOf(containsString("user"), containsString("user,foo,bar"))));
}
@Test
public void testListUsersAndRoles_Cmd_listSingleUser() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
File usersFile = writeFile("admin:{plain}changeme\nuser:{plain}changeme\nno-roles-user:{plain}changeme\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.put("shield.authc.esusers.files.users", usersFile)
.build();
LoggingTerminal catchTerminalOutput = new LoggingTerminal();
ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, "admin");
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(catchTerminalOutput.getTerminalOutput(), hasSize(greaterThanOrEqualTo(1)));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(containsString("admin")));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(not(containsString("user"))));
}
@Test
public void testListUsersAndRoles_Cmd_listSingleUserNotFound() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.build();
LoggingTerminal catchTerminalOutput = new LoggingTerminal();
ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, "does-not-exist");
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.NO_USER));
}
@Test
public void testListUsersAndRoles_Cmd_testThatUsersWithoutRolesAreListed() throws Exception {
File usersFile = writeFile("admin:{plain}changeme\nuser:{plain}changeme\nno-roles-user:{plain}changeme\n");
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.put("shield.authc.esusers.files.users", usersFile)
.build();
LoggingTerminal catchTerminalOutput = new LoggingTerminal();
ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, null);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(catchTerminalOutput.getTerminalOutput(), hasSize(greaterThanOrEqualTo(3)));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(containsString("admin")));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(allOf(containsString("user"), containsString("user,foo,bar"))));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(allOf(containsString("no-roles-user"), containsString("-"))));
}
@Test
public void testListUsersAndRoles_Cmd_testThatUsersWithoutRolesAreListedForSingleUser() throws Exception {
File usersFile = writeFile("admin:{plain}changeme");
File usersRoleFile = writeFile("");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.put("shield.authc.esusers.files.users", usersFile)
.build();
LoggingTerminal loggingTerminal = new LoggingTerminal();
ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(loggingTerminal, "admin");
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(loggingTerminal.getTerminalOutput(), hasSize(greaterThanOrEqualTo(1)));
assertThat(loggingTerminal.getTerminalOutput(), hasItem(allOf(containsString("admin"), containsString("-"))));
}
public static class LoggingTerminal extends TerminalMock {
List<String> terminalOutput = new ArrayList();
public LoggingTerminal() {
super(Verbosity.NORMAL);
}
public LoggingTerminal(Verbosity verbosity) {
super(verbosity);
}
@Override
protected void doPrint(String msg, Object... args) {
terminalOutput.add(String.format(Locale.ROOT, msg, args));
}
@Override
public void print(String msg, Object... args) {
doPrint(msg, args);
}
public List<String> getTerminalOutput() {
return terminalOutput;
}
}
private CliTool.ExitStatus execute(CliTool.Command cmd, Settings settings) throws Exception {
Environment env = new Environment(settings);
return cmd.execute(settings, env);
}
private File writeFile(String content) throws IOException {
File file = temporaryFolder.newFile();
Files.write(content.getBytes(Charsets.UTF_8), file);
return file;
}
private void assertFileExists(File file) {
assertThat(String.format(Locale.ROOT, "Expected file [%s] to exist", file), file.exists(), is(true));
}
}