|
@@ -12,6 +12,7 @@ import org.elasticsearch.ElasticsearchParseException;
|
|
|
import org.elasticsearch.common.Nullable;
|
|
|
import org.elasticsearch.common.component.AbstractComponent;
|
|
|
import org.elasticsearch.common.settings.Settings;
|
|
|
+import org.elasticsearch.common.util.set.Sets;
|
|
|
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
|
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
|
|
import org.elasticsearch.common.xcontent.XContentParser;
|
|
@@ -34,13 +35,16 @@ import java.nio.charset.StandardCharsets;
|
|
|
import java.nio.file.Files;
|
|
|
import java.nio.file.Path;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.HashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
import java.util.Set;
|
|
|
+import java.util.function.Consumer;
|
|
|
import java.util.regex.Pattern;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
import static java.util.Collections.emptyMap;
|
|
|
import static java.util.Collections.unmodifiableMap;
|
|
@@ -52,16 +56,16 @@ public class FileRolesStore extends AbstractComponent {
|
|
|
|
|
|
private final Path file;
|
|
|
private final XPackLicenseState licenseState;
|
|
|
- private final List<Runnable> listeners = new ArrayList<>();
|
|
|
+ private final List<Consumer<Set<String>>> listeners = new ArrayList<>();
|
|
|
|
|
|
private volatile Map<String, RoleDescriptor> permissions;
|
|
|
|
|
|
public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, XPackLicenseState licenseState)
|
|
|
throws IOException {
|
|
|
- this(settings, env, watcherService, () -> {}, licenseState);
|
|
|
+ this(settings, env, watcherService, null, licenseState);
|
|
|
}
|
|
|
|
|
|
- FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, Runnable listener,
|
|
|
+ FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, Consumer<Set<String>> listener,
|
|
|
XPackLicenseState licenseState) throws IOException {
|
|
|
super(settings);
|
|
|
this.file = resolveFile(env);
|
|
@@ -76,9 +80,10 @@ public class FileRolesStore extends AbstractComponent {
|
|
|
}
|
|
|
|
|
|
public Set<RoleDescriptor> roleDescriptors(Set<String> roleNames) {
|
|
|
+ final Map<String, RoleDescriptor> localPermissions = permissions;
|
|
|
Set<RoleDescriptor> descriptors = new HashSet<>();
|
|
|
roleNames.forEach((name) -> {
|
|
|
- RoleDescriptor descriptor = permissions.get(name);
|
|
|
+ RoleDescriptor descriptor = localPermissions.get(name);
|
|
|
if (descriptor != null) {
|
|
|
descriptors.add(descriptor);
|
|
|
}
|
|
@@ -87,12 +92,13 @@ public class FileRolesStore extends AbstractComponent {
|
|
|
}
|
|
|
|
|
|
public Map<String, Object> usageStats() {
|
|
|
+ final Map<String, RoleDescriptor> localPermissions = permissions;
|
|
|
Map<String, Object> usageStats = new HashMap<>(3);
|
|
|
- usageStats.put("size", permissions.size());
|
|
|
+ usageStats.put("size", localPermissions.size());
|
|
|
|
|
|
boolean dls = false;
|
|
|
boolean fls = false;
|
|
|
- for (RoleDescriptor descriptor : permissions.values()) {
|
|
|
+ for (RoleDescriptor descriptor : localPermissions.values()) {
|
|
|
for (IndicesPrivileges indicesPrivileges : descriptor.getIndicesPrivileges()) {
|
|
|
fls = fls || indicesPrivileges.getGrantedFields() != null || indicesPrivileges.getDeniedFields() != null;
|
|
|
dls = dls || indicesPrivileges.getQuery() != null;
|
|
@@ -107,10 +113,10 @@ public class FileRolesStore extends AbstractComponent {
|
|
|
return usageStats;
|
|
|
}
|
|
|
|
|
|
- public void addListener(Runnable runnable) {
|
|
|
- Objects.requireNonNull(runnable);
|
|
|
+ public void addListener(Consumer<Set<String>> consumer) {
|
|
|
+ Objects.requireNonNull(consumer);
|
|
|
synchronized (this) {
|
|
|
- listeners.add(runnable);
|
|
|
+ listeners.add(consumer);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -118,6 +124,11 @@ public class FileRolesStore extends AbstractComponent {
|
|
|
return file;
|
|
|
}
|
|
|
|
|
|
+ // package private for testing
|
|
|
+ Set<String> getAllRoleNames() {
|
|
|
+ return permissions.keySet();
|
|
|
+ }
|
|
|
+
|
|
|
public static Path resolveFile(Environment env) {
|
|
|
return XPackPlugin.resolveConfigFile(env, "roles.yml");
|
|
|
}
|
|
@@ -319,11 +330,13 @@ public class FileRolesStore extends AbstractComponent {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void onFileChanged(Path file) {
|
|
|
+ public synchronized void onFileChanged(Path file) {
|
|
|
if (file.equals(FileRolesStore.this.file)) {
|
|
|
+ final Map<String, RoleDescriptor> previousPermissions = permissions;
|
|
|
try {
|
|
|
permissions = parseFile(file, logger, settings, licenseState);
|
|
|
- logger.info("updated roles (roles file [{}] {})", file.toAbsolutePath(), Files.exists(file) ? "changed" : "removed");
|
|
|
+ logger.info("updated roles (roles file [{}] {})", file.toAbsolutePath(),
|
|
|
+ Files.exists(file) ? "changed" : "removed");
|
|
|
} catch (Exception e) {
|
|
|
logger.error(
|
|
|
(Supplier<?>) () -> new ParameterizedMessage(
|
|
@@ -331,9 +344,13 @@ public class FileRolesStore extends AbstractComponent {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- synchronized (FileRolesStore.this) {
|
|
|
- listeners.forEach(Runnable::run);
|
|
|
- }
|
|
|
+ final Set<String> changedOrMissingRoles = Sets.difference(previousPermissions.entrySet(), permissions.entrySet())
|
|
|
+ .stream()
|
|
|
+ .map(Map.Entry::getKey)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ final Set<String> addedRoles = Sets.difference(permissions.keySet(), previousPermissions.keySet());
|
|
|
+ final Set<String> changedRoles = Collections.unmodifiableSet(Sets.union(changedOrMissingRoles, addedRoles));
|
|
|
+ listeners.forEach(c -> c.accept(changedRoles));
|
|
|
}
|
|
|
}
|
|
|
}
|