Skip to content

Commit 0da7d01

Browse files
authored
Allow resolving dependencies for multiple entities at once (#23268)
1 parent 8196e3e commit 0da7d01

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

graylog2-server/src/main/java/org/graylog/security/entities/DefaultEntityDependencyResolver.java

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.graylog.grn.GRNDescriptorService;
2424
import org.graylog.grn.GRNRegistry;
2525
import org.graylog.grn.GRNType;
26-
import org.graylog.grn.GRNTypes;
2726
import org.graylog.security.DBGrantService;
2827
import org.graylog.security.shares.Grantee;
2928
import org.graylog2.contentpacks.ContentPackService;
@@ -32,6 +31,7 @@
3231
import org.graylog2.contentpacks.model.ModelTypes;
3332

3433
import javax.annotation.Nullable;
34+
import java.util.Collection;
3535
import java.util.Collections;
3636
import java.util.Map;
3737
import java.util.Optional;
@@ -45,13 +45,6 @@ public class DefaultEntityDependencyResolver implements EntityDependencyResolver
4545
private final GRNRegistry grnRegistry;
4646
private final GRNDescriptorService descriptorService;
4747
private final DBGrantService grantService;
48-
// Some dependencies can be ignored.
49-
// E.g. To view a stream with a custom output, a user does not need output permissions
50-
private static final Map<GRNType, Set<ModelType>> IGNORED_DEPENDENCIES = ImmutableMap.<GRNType, Set<ModelType>>builder()
51-
.put(GRNTypes.SEARCH, ImmutableSet.of(ModelTypes.OUTPUT_V1))
52-
.put(GRNTypes.STREAM, ImmutableSet.of(ModelTypes.OUTPUT_V1))
53-
.put(GRNTypes.DASHBOARD, ImmutableSet.of(ModelTypes.OUTPUT_V1))
54-
.build();
5548

5649
@Inject
5750
public DefaultEntityDependencyResolver(ContentPackService contentPackService,
@@ -66,22 +59,31 @@ public DefaultEntityDependencyResolver(ContentPackService contentPackService,
6659

6760
@Override
6861
public ImmutableSet<EntityDescriptor> resolve(GRN entity) {
69-
final var contentPackEntityDescriptor = toContentPackEntityDescriptor(entity);
70-
final ImmutableSet<GRN> dependencies = contentPackService.resolveEntities(Set.of(contentPackEntityDescriptor)).stream()
71-
.filter(dep -> {
72-
// Filter dependencies that aren't needed for grants sharing
73-
// TODO This is another reason why we shouldn't be using the content pack resolver ¯\_(ツ)_/¯
74-
final Set<ModelType> ignoredDeps = IGNORED_DEPENDENCIES.getOrDefault(entity.grnType(), ImmutableSet.of());
75-
return !ignoredDeps.contains(dep.type());
76-
})
62+
return resolve(Set.of(entity));
63+
}
64+
65+
@SuppressWarnings("UnstableApiUsage")
66+
protected ImmutableSet<EntityDescriptor> resolve(Collection<GRN> entities) {
67+
final var cpDescriptors = entities.stream().map(DefaultEntityDependencyResolver::toContentPackEntityDescriptor)
68+
.collect(Collectors.toUnmodifiableSet());
69+
final var dependencyGraph = contentPackService.resolveEntityDependencyGraph(cpDescriptors);
70+
final var dependencies = dependencyGraph.nodes().stream()
71+
.filter(dependency -> !cpDescriptors.contains(dependency)) // Don't include the given entity in dependencies
72+
// Workaround to ignore outputs as dependencies of streams.
73+
// To view a stream with a custom output, a user does not need output permissions. Therefore, we
74+
// ignore outputs if they only appear as dependencies of streams.
75+
// TODO This is another reason why we shouldn't be using the content pack resolver ¯\_(ツ)_/¯
76+
.filter(dependency -> !ModelTypes.OUTPUT_V1.equals(dependency.type()) ||
77+
dependencyGraph.predecessors(dependency).stream().anyMatch(
78+
predecessor -> !ModelTypes.STREAM_V1.equals(predecessor.type()))
79+
)
7780
// TODO: Work around from using the content pack dependency resolver:
7881
// We've added stream_title content pack entities in https://github.com/Graylog2/graylog2-server/pull/17089,
7982
// but in this context we want to return the actual dependent Stream to add additional permissions to.
8083
.map(cpDescriptor -> ModelTypes.STREAM_REF_V1.equals(cpDescriptor.type())
8184
? org.graylog2.contentpacks.model.entities.EntityDescriptor.create(cpDescriptor.id(), ModelTypes.STREAM_V1)
8285
: cpDescriptor)
8386
.map(cpDescriptor -> grnRegistry.newGRN(cpDescriptor.type().name(), cpDescriptor.id().id()))
84-
.filter(dependency -> !entity.equals(dependency)) // Don't include the given entity in dependencies
8587
.collect(ImmutableSet.toImmutableSet());
8688

8789
final ImmutableMap<GRN, Optional<String>> entityExcerpts = entityExcerpts();

graylog2-server/src/main/java/org/graylog2/contentpacks/ContentPackService.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@
3030
import jakarta.inject.Inject;
3131
import jakarta.inject.Singleton;
3232
import jakarta.ws.rs.ForbiddenException;
33-
import org.apache.shiro.authz.annotation.Logical;
33+
import org.graylog.security.GrantDTO;
3434
import org.graylog.security.UserContext;
3535
import org.graylog.security.UserContextMissingException;
36-
import org.graylog.security.GrantDTO;
3736
import org.graylog2.Configuration;
3837
import org.graylog2.contentpacks.constraints.ConstraintChecker;
3938
import org.graylog2.contentpacks.exceptions.ContentPackException;
@@ -397,6 +396,10 @@ public Map<String, EntityExcerpt> getEntityExcerpts() {
397396
}
398397

399398
public Set<EntityDescriptor> resolveEntities(Collection<EntityDescriptor> unresolvedEntities) {
399+
return resolveEntityDependencyGraph(unresolvedEntities).nodes();
400+
}
401+
402+
public Graph<EntityDescriptor> resolveEntityDependencyGraph(Collection<EntityDescriptor> unresolvedEntities) {
400403
final MutableGraph<EntityDescriptor> dependencyGraph = GraphBuilder.directed()
401404
.allowsSelfLoops(false)
402405
.nodeOrder(ElementOrder.insertion())
@@ -408,7 +411,7 @@ public Set<EntityDescriptor> resolveEntities(Collection<EntityDescriptor> unreso
408411

409412
LOG.debug("Final dependency graph: {}", finalDependencyGraph);
410413

411-
return finalDependencyGraph.nodes();
414+
return finalDependencyGraph;
412415
}
413416

414417
private MutableGraph<EntityDescriptor> resolveDependencyGraph(Graph<EntityDescriptor> dependencyGraph, Set<EntityDescriptor> resolvedEntities) {

graylog2-server/src/test/java/org/graylog/security/entities/DefaultEntityDependencyResolverTest.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package org.graylog.security.entities;
1818

1919
import com.google.common.collect.ImmutableSet;
20+
import com.google.common.graph.GraphBuilder;
2021
import org.graylog.grn.GRN;
2122
import org.graylog.grn.GRNDescriptor;
2223
import org.graylog.grn.GRNDescriptorService;
2324
import org.graylog.grn.GRNRegistry;
2425
import org.graylog.grn.GRNType;
26+
import org.graylog.grn.GRNTypes;
2527
import org.graylog.security.DBGrantService;
2628
import org.graylog.testing.GRNExtension;
2729
import org.graylog.testing.mongodb.MongoDBExtension;
@@ -46,6 +48,7 @@
4648
import static org.mockito.ArgumentMatchers.any;
4749
import static org.mockito.Mockito.when;
4850

51+
@SuppressWarnings("UnstableApiUsage")
4952
@ExtendWith(MongoDBExtension.class)
5053
@ExtendWith(MongoJackExtension.class)
5154
@ExtendWith(GRNExtension.class)
@@ -83,7 +86,9 @@ void resolve() {
8386
when(contentPackService.listAllEntityExcerpts()).thenReturn(ImmutableSet.of(streamExcerpt));
8487

8588
final EntityDescriptor streamDescriptor = EntityDescriptor.builder().type(ModelTypes.STREAM_V1).id(ModelId.of("54e3deadbeefdeadbeefaffe")).build();
86-
when(contentPackService.resolveEntities(any())).thenReturn(ImmutableSet.of(streamDescriptor));
89+
final var dependencyGraph = GraphBuilder.directed().<EntityDescriptor>build();
90+
dependencyGraph.addNode(streamDescriptor);
91+
when(contentPackService.resolveEntityDependencyGraph(any())).thenReturn(dependencyGraph);
8792

8893
when(grnDescriptorService.getDescriptor(any(GRN.class))).thenAnswer(a -> {
8994
GRN grnArg = a.getArgument(0);
@@ -108,7 +113,9 @@ void resolveWithInclompleteDependency() {
108113

109114
when(contentPackService.listAllEntityExcerpts()).thenReturn(ImmutableSet.of());
110115
final EntityDescriptor streamDescriptor = EntityDescriptor.builder().type(ModelTypes.STREAM_V1).id(ModelId.of("54e3deadbeefdeadbeefaffe")).build();
111-
when(contentPackService.resolveEntities(any())).thenReturn(ImmutableSet.of(streamDescriptor));
116+
final var dependencyGraph = GraphBuilder.directed().<EntityDescriptor>build();
117+
dependencyGraph.addNode(streamDescriptor);
118+
when(contentPackService.resolveEntityDependencyGraph(any())).thenReturn(dependencyGraph);
112119

113120
when(grnDescriptorService.getDescriptor(any(GRN.class))).thenAnswer(a -> {
114121
GRN grnArg = a.getArgument(0);
@@ -139,7 +146,9 @@ void resolveStreamReference() {
139146
when(contentPackService.listAllEntityExcerpts()).thenReturn(ImmutableSet.of(streamExcerpt, streamRefExcerpt));
140147

141148
final EntityDescriptor streamDescriptor = EntityDescriptor.builder().type(ModelTypes.STREAM_REF_V1).id(ModelId.of("54e3deadbeefdeadbeefaffe")).build();
142-
when(contentPackService.resolveEntities(any())).thenReturn(ImmutableSet.of(streamDescriptor));
149+
final var dependencyGraph = GraphBuilder.directed().<EntityDescriptor>build();
150+
dependencyGraph.addNode(streamDescriptor);
151+
when(contentPackService.resolveEntityDependencyGraph(any())).thenReturn(dependencyGraph);
143152

144153
when(grnDescriptorService.getDescriptor(any(GRN.class))).thenAnswer(a -> {
145154
GRN grnArg = a.getArgument(0);
@@ -163,11 +172,41 @@ void resolveStreamReference() {
163172
void resolveEventProcedureDependency() {
164173
final EntityDescriptor definitionDescriptor = EntityDescriptor.builder().type(ModelTypes.EVENT_DEFINITION_V1).id(ModelId.of("54e3deadbeefdeadbeefafff")).build();
165174
final EntityDescriptor procedureDescriptor = EntityDescriptor.builder().type(ModelTypes.EVENT_PROCEDURE_V1).id(ModelId.of("54e3deadbeefdeadbeefaffe")).build();
166-
when(contentPackService.resolveEntities(any())).thenReturn(ImmutableSet.of(definitionDescriptor, procedureDescriptor));
175+
final var dependencyGraph = GraphBuilder.directed().<EntityDescriptor>build();
176+
dependencyGraph.addNode(definitionDescriptor);
177+
dependencyGraph.putEdge(definitionDescriptor, procedureDescriptor);
178+
when(contentPackService.resolveEntityDependencyGraph(any())).thenReturn(dependencyGraph);
167179

168180
final GRN definitionGrn = grnRegistry.newGRN("event_definition", "54e3deadbeefdeadbeefafff");
169181
grnRegistry.registerType(GRNType.create("event_procedure"));
170182
final ImmutableSet<org.graylog.security.entities.EntityDescriptor> missingDependencies = entityDependencyResolver.resolve(definitionGrn);
171183
assertThat(missingDependencies).hasSize(1);
172184
}
185+
186+
@Test
187+
@DisplayName("Try to resolve with an output dependency")
188+
void resolveWithOutputDependency() {
189+
final var output1 = EntityDescriptor.builder().type(ModelTypes.OUTPUT_V1).id(ModelId.of("output-1-id")).build();
190+
final var output2 = EntityDescriptor.builder().type(ModelTypes.OUTPUT_V1).id(ModelId.of("output-2-id")).build();
191+
final var stream = EntityDescriptor.builder().type(ModelTypes.STREAM_V1).id(ModelId.of("stream-id")).build();
192+
// just for testing purposes, let's assume we'd allow event definitions to depend directly on outputs
193+
final var dashboard = EntityDescriptor.builder().type(ModelTypes.EVENT_DEFINITION_V1).id(ModelId.of("event-definition-id")).build();
194+
195+
// we'll resolve this dependency graph for whatever entity we pass in
196+
final var dependencyGraph = GraphBuilder.directed().<EntityDescriptor>build();
197+
dependencyGraph.addNode(stream);
198+
dependencyGraph.addNode(dashboard);
199+
dependencyGraph.putEdge(stream, output1);
200+
dependencyGraph.putEdge(dashboard, output2);
201+
when(contentPackService.resolveEntityDependencyGraph(any())).thenReturn(dependencyGraph);
202+
203+
// output1 should be ignored because it is only a dependency of the stream
204+
final var dependencies = entityDependencyResolver.resolve(grnRegistry.newGRN(GRNTypes.DASHBOARD, "dashboard-id"));
205+
assertThat(dependencies)
206+
.extracting(org.graylog.security.entities.EntityDescriptor::id)
207+
.containsExactlyInAnyOrder(grnRegistry.newGRN(GRNTypes.EVENT_DEFINITION, "event-definition-id"),
208+
grnRegistry.newGRN(GRNTypes.STREAM, "stream-id"),
209+
grnRegistry.newGRN(GRNTypes.OUTPUT, "output-2-id")
210+
);
211+
}
173212
}

0 commit comments

Comments
 (0)