diff --git a/changelog/unreleased/pr-24080.toml b/changelog/unreleased/pr-24080.toml new file mode 100644 index 0000000000..900fc3891f --- /dev/null +++ b/changelog/unreleased/pr-24080.toml @@ -0,0 +1,5 @@ +type = "f" +message = "Fixing recent activity for non-admins: Including update events to accessible entities." + +pulls = ["24080"] +issues = ["Graylog2/graylog-plugin-enterprise#12427"] diff --git a/graylog2-server/src/main/java/org/graylog/events/processor/DBEventDefinitionService.java b/graylog2-server/src/main/java/org/graylog/events/processor/DBEventDefinitionService.java index db9307a331..e461babf99 100644 --- a/graylog2-server/src/main/java/org/graylog/events/processor/DBEventDefinitionService.java +++ b/graylog2-server/src/main/java/org/graylog/events/processor/DBEventDefinitionService.java @@ -79,7 +79,9 @@ public class DBEventDefinitionService { @Inject public DBEventDefinitionService(MongoCollections mongoCollections, DBEventProcessorStateService stateService, - EntityRegistrar entityRegistrar, EntityScopeService entityScopeService, SearchFiltersReFetcher searchFiltersRefetcher) { + EntityRegistrar entityRegistrar, + EntityScopeService entityScopeService, + SearchFiltersReFetcher searchFiltersRefetcher) { this.collection = mongoCollections.collection(COLLECTION_NAME, EventDefinitionDto.class); this.mongoUtils = mongoCollections.utils(collection); this.scopedEntityMongoUtils = mongoCollections.scopedEntityUtils(collection, entityScopeService); diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/startpage/recentActivities/RecentActivityService.java b/graylog2-server/src/main/java/org/graylog/plugins/views/startpage/recentActivities/RecentActivityService.java index 59e5246679..e1c04bea81 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/startpage/recentActivities/RecentActivityService.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/startpage/recentActivities/RecentActivityService.java @@ -16,27 +16,37 @@ */ package org.graylog.plugins.views.startpage.recentActivities; +import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; import com.mongodb.BasicDBObject; -import jakarta.inject.Singleton; -import org.graylog2.database.MongoCollection; import com.mongodb.client.model.CreateCollectionOptions; import com.mongodb.client.model.Filters; import jakarta.inject.Inject; +import jakarta.inject.Singleton; import org.graylog.grn.GRN; import org.graylog.grn.GRNRegistry; import org.graylog.grn.GRNType; import org.graylog.grn.GRNTypes; import org.graylog.plugins.views.search.permissions.SearchUser; +import org.graylog.security.Capability; +import org.graylog.security.CapabilityRegistry; +import org.graylog.security.DBGrantService; +import org.graylog.security.GrantDTO; import org.graylog.security.PermissionAndRoleResolver; +import org.graylog.security.shares.GranteeService; +import org.graylog.security.shares.PluggableEntityService; +import org.graylog2.database.MongoCollection; import org.graylog2.database.MongoCollections; import org.graylog2.database.MongoConnection; import org.graylog2.database.PaginatedList; import org.graylog2.database.pagination.MongoPaginationHelper; import org.graylog2.plugin.database.users.User; +import org.graylog2.plugin.security.Permission; import org.graylog2.rest.models.SortOrder; import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; @Singleton public class RecentActivityService { @@ -48,14 +58,23 @@ public class RecentActivityService { private static final long MAXIMUM_RECENT_ACTIVITIES = 10000; private final MongoCollection db; private final MongoPaginationHelper pagination; + private final DBGrantService grantService; + private final GranteeService granteeService; + private final PluggableEntityService pluggableEntityService; + private CapabilityRegistry capabilityRegistry; @Inject public RecentActivityService(final MongoCollections mongoCollections, final MongoConnection mongoConnection, final EventBus eventBus, final GRNRegistry grnRegistry, - final PermissionAndRoleResolver permissionAndRoleResolver) { - this(mongoCollections, mongoConnection, eventBus, grnRegistry, permissionAndRoleResolver, MAXIMUM_RECENT_ACTIVITIES); + final PermissionAndRoleResolver permissionAndRoleResolver, + final DBGrantService grantService, + final GranteeService granteeService, + final PluggableEntityService pluggableEntityService, + final CapabilityRegistry capabilityRegistry) { + this(mongoCollections, mongoConnection, eventBus, grnRegistry, permissionAndRoleResolver, MAXIMUM_RECENT_ACTIVITIES, + grantService, granteeService, pluggableEntityService, capabilityRegistry); } /* @@ -66,7 +85,15 @@ public class RecentActivityService { final EventBus eventBus, final GRNRegistry grnRegistry, final PermissionAndRoleResolver permissionAndRoleResolver, - final long maximum) { + final long maximum, + final DBGrantService grantService, + final GranteeService granteeService, + final PluggableEntityService pluggableEntityService, + final CapabilityRegistry capabilityRegistry) { + this.grantService = grantService; + this.granteeService = granteeService; + this.pluggableEntityService = pluggableEntityService; + this.capabilityRegistry = capabilityRegistry; final var mongodb = mongoConnection.getMongoDatabase(); if (!mongodb.listCollectionNames().into(new HashSet<>()).contains(COLLECTION_NAME)) { mongodb.createCollection(COLLECTION_NAME, new CreateCollectionOptions().capped(true).sizeInBytes(maximum * 1024).maxDocuments(maximum)); @@ -122,15 +149,33 @@ public class RecentActivityService { // filter relevant activities by permissions final var principal = grnRegistry.newGRN(GRNTypes.USER, user.getUser().getId()); - final var grns = permissionAndRoleResolver.resolveGrantees(principal).stream().map(GRN::toString).toList(); - var query = Filters.in(RecentActivityDTO.FIELD_GRANTEE, grns); + final var grantees = permissionAndRoleResolver.resolveGrantees(principal).stream().map(GRN::toString).toList(); + final var sharedEntities = getShareGRNsFor(principal); return pagination .perPage(perPage) .sort(sort) - .filter(query) .includeGrandTotal(true) - .grandTotalFilter(query) - .page(page); + .page(page, activity -> { + final var itemGRN = activity.itemGrn(); + final var hasAnyPermission = capabilityRegistry.getPermissions(Capability.VIEW, itemGRN.grnType()) + .stream() + .map(Permission::permission) + .anyMatch(permission -> user.isPermitted(permission, itemGRN.entity())); + return hasAnyPermission || grantees.contains(activity.grantee()) || sharedEntities.contains(itemGRN); + }); + } + + private Set getShareGRNsFor(GRN grantee) { + // Get all aliases for the grantee to make sure we find all entities the grantee has access to + final Set granteeAliases = granteeService.getGranteeAliases(grantee); + + final ImmutableSet grants = grantService.getForGranteesOrGlobalWithCapability(granteeAliases, Capability.VIEW); + + return grants.stream() + .map(GrantDTO::target) + .flatMap(pluggableEntityService::expand) + .filter(pluggableEntityService.excludeTypesFilter()) + .collect(Collectors.toSet()); } public void deleteAllEntriesForEntity(GRN grn) { diff --git a/graylog2-server/src/test/java/org/graylog/plugins/views/startpage/StartPageServiceTest.java b/graylog2-server/src/test/java/org/graylog/plugins/views/startpage/StartPageServiceTest.java index c493e71d42..6a4e6f2a5c 100644 --- a/graylog2-server/src/test/java/org/graylog/plugins/views/startpage/StartPageServiceTest.java +++ b/graylog2-server/src/test/java/org/graylog/plugins/views/startpage/StartPageServiceTest.java @@ -31,7 +31,11 @@ import org.graylog.plugins.views.startpage.lastOpened.LastOpenedForUserDTO; import org.graylog.plugins.views.startpage.lastOpened.LastOpenedService; import org.graylog.plugins.views.startpage.recentActivities.RecentActivityService; import org.graylog.plugins.views.startpage.title.StartPageItemTitleRetriever; +import org.graylog.security.CapabilityRegistry; +import org.graylog.security.DBGrantService; import org.graylog.security.PermissionAndRoleResolver; +import org.graylog.security.shares.GranteeService; +import org.graylog.security.shares.PluggableEntityService; import org.graylog.testing.GRNExtension; import org.graylog.testing.TestUserServiceExtension; import org.graylog.testing.mongodb.MongoDBExtension; @@ -75,11 +79,9 @@ public class StartPageServiceTest { MongoCollections mongoCollections, GRNRegistry grnRegistry) { - var admin = TestUser.builder().withId("637748db06e1d74da0a54331").withUsername("local:admin").isLocalAdmin(true).build(); var user = TestUser.builder().withId("637748db06e1d74da0a54330").withUsername("test").isLocalAdmin(false).build(); this.searchUser = TestSearchUser.builder().withUser(user).build(); this.grnRegistry = grnRegistry; - var searchAdmin = TestSearchUser.builder().withUser(admin).build(); var permissionAndRoleResolver = new PermissionAndRoleResolver() { @Override @@ -101,7 +103,8 @@ public class StartPageServiceTest { var eventbus = new EventBus(); final var connection = mongodb.mongoConnection(); var lastOpenedService = new LastOpenedService(mongoCollections, eventbus); - var recentActivityService = new RecentActivityService(mongoCollections, connection, eventbus, grnRegistry, permissionAndRoleResolver); + var recentActivityService = new RecentActivityService(mongoCollections, connection, eventbus, grnRegistry, permissionAndRoleResolver, + new DBGrantService(mongoCollections), mock(GranteeService.class), new PluggableEntityService(Set.of()), mock(CapabilityRegistry.class)); catalog = mock(Catalog.class); doReturn(Optional.of(new Catalog.Entry("", ""))).when(catalog).getEntry(any()); startPageService = new StartPageService(grnRegistry, lastOpenedService, recentActivityService, eventbus, new StartPageItemTitleRetriever(catalog, Map.of())); diff --git a/graylog2-server/src/test/java/org/graylog/plugins/views/startpage/recentActivities/RecentActivityServiceTest.java b/graylog2-server/src/test/java/org/graylog/plugins/views/startpage/recentActivities/RecentActivityServiceTest.java index 906918480a..6ce0f4fb45 100644 --- a/graylog2-server/src/test/java/org/graylog/plugins/views/startpage/recentActivities/RecentActivityServiceTest.java +++ b/graylog2-server/src/test/java/org/graylog/plugins/views/startpage/recentActivities/RecentActivityServiceTest.java @@ -23,7 +23,11 @@ import org.graylog.grn.GRNTypes; import org.graylog.plugins.views.search.permissions.SearchUser; import org.graylog.plugins.views.search.rest.TestSearchUser; import org.graylog.plugins.views.search.rest.TestUser; +import org.graylog.security.CapabilityRegistry; +import org.graylog.security.DBGrantService; import org.graylog.security.PermissionAndRoleResolver; +import org.graylog.security.shares.GranteeService; +import org.graylog.security.shares.PluggableEntityService; import org.graylog.testing.GRNExtension; import org.graylog.testing.TestUserService; import org.graylog.testing.TestUserServiceExtension; @@ -40,6 +44,7 @@ import java.util.Objects; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; @ExtendWith(MongoDBExtension.class) @ExtendWith(MongoJackExtension.class) @@ -89,12 +94,18 @@ public class RecentActivityServiceTest { this.testUserService = testUserService; this.grnRegistry = grnRegistry; - this.recentActivityService = new RecentActivityService(mongoCollections, + this.recentActivityService = new RecentActivityService( + mongoCollections, mongodb.mongoConnection(), null, grnRegistry, permissionAndRoleResolver, - MAXIMUM); + MAXIMUM, + new DBGrantService(mongoCollections), + mock(GranteeService.class), + new PluggableEntityService(Set.of()), + mock(CapabilityRegistry.class) + ); } @Test diff --git a/graylog2-web-interface/src/hooks/useHasEntityPermissionByGRN.ts b/graylog2-web-interface/src/hooks/useHasEntityPermissionByGRN.ts index 5101a97dc9..e38042de47 100644 --- a/graylog2-web-interface/src/hooks/useHasEntityPermissionByGRN.ts +++ b/graylog2-web-interface/src/hooks/useHasEntityPermissionByGRN.ts @@ -17,18 +17,17 @@ import { useMemo } from 'react'; -import useCurrentUser from 'hooks/useCurrentUser'; import getPermissionPrefixByType from 'util/getPermissionPrefixByType'; -import { isPermitted } from 'util/PermissionsMixin'; import { getValuesFromGRN } from 'logic/permissions/GRN'; +import usePermissions from 'hooks/usePermissions'; const useHasEntityPermissionByGRN = (grn: string, permissionType: string = 'read') => { const { id, type } = getValuesFromGRN(grn); - const { permissions } = useCurrentUser(); + const { isPermitted } = usePermissions(); return useMemo( - () => isPermitted(permissions, `${getPermissionPrefixByType(type, id)}${permissionType}:${id}`), - [id, permissionType, permissions, type], + () => isPermitted(`${getPermissionPrefixByType(type, id)}${permissionType}:${id}`), + [id, permissionType, isPermitted, type], ); }; diff --git a/graylog2-web-interface/src/logic/permissions/StandardEntityPermissionsMapper.ts b/graylog2-web-interface/src/logic/permissions/StandardEntityPermissionsMapper.ts index 9f0ff42c01..a01b68c585 100644 --- a/graylog2-web-interface/src/logic/permissions/StandardEntityPermissionsMapper.ts +++ b/graylog2-web-interface/src/logic/permissions/StandardEntityPermissionsMapper.ts @@ -37,6 +37,7 @@ const typePrefixCornerCasesMap = { event_definition: 'eventdefinitions:', notification: 'eventnotifications:', search: 'view:', + dashboard: 'view:', }; const standardEntityPermissionsMapper: EntityPermissionsMapper = {