Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Splitting usage of SearchUser into interfaces in WidgetExecutor #19154

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
import org.graylog.plugins.views.search.errors.PermissionException;
import org.graylog.plugins.views.search.permissions.SearchPermissions;
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.plugins.views.search.permissions.ViewPermissions;
import org.graylog.plugins.views.search.views.ViewDTO;
import org.graylog.plugins.views.search.views.ViewResolver;
import org.graylog.plugins.views.search.views.ViewService;

import jakarta.inject.Inject;
import org.graylog.security.UserDetails;

import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -52,16 +54,20 @@ public SearchDomain(SearchDbService dbService,
}

public Optional<Search> getForUser(String id, SearchUser searchUser) {
return this.getForUser(id, searchUser, searchUser, searchUser);
}

public Optional<Search> getForUser(String id, ViewPermissions viewPermissions, SearchPermissions searchPermissions, UserDetails name) {
final Optional<Search> search = dbService.get(id);

search.ifPresent(s -> checkPermission(searchUser, s));
search.ifPresent(s -> checkPermission(viewPermissions, searchPermissions, name, s));

return search;
}

private void checkPermission(SearchUser searchUser, Search search) {
if (!hasReadPermissionFor(searchUser, searchUser::canReadView, search)) {
throw new PermissionException("User " + searchUser.username() + " does not have permission to load search " + search.id());
private void checkPermission(ViewPermissions viewPermissions, SearchPermissions searchPermissions, UserDetails name, Search search) {
if (!hasReadPermissionFor(searchPermissions, viewPermissions::canReadView, search)) {
throw new PermissionException("User " + name.username() + " does not have permission to load search " + search.id());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@
import org.graylog.plugins.views.search.engine.normalization.SearchNormalization;
import org.graylog.plugins.views.search.engine.validation.SearchValidation;
import org.graylog.plugins.views.search.errors.SearchError;
import org.graylog.plugins.views.search.permissions.SearchPermissions;
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.plugins.views.search.permissions.ViewPermissions;
import org.graylog.plugins.views.search.rest.ExecutionState;
import org.graylog.security.HasPermissions;
import org.graylog.security.UserDetails;
import org.graylog.security.HasStreams;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -64,21 +69,29 @@ public SearchExecutor(SearchDomain searchDomain,
this.searchNormalization = searchNormalization;
}

public SearchJob executeSync(String searchId, SearchUser searchUser, ExecutionState executionState) {
return searchDomain.getForUser(searchId, searchUser)
.map(s -> executeSync(s, searchUser, executionState))
public SearchJob executeSync(String searchId, ViewPermissions viewPermissions, SearchPermissions searchPermissions, HasPermissions permissions, HasStreams streams, UserDetails name, ExecutionState executionState) {
return searchDomain.getForUser(searchId, viewPermissions, searchPermissions, name)
.map(s -> executeSync(s, permissions, streams, name, executionState))
.orElseThrow(() -> new NotFoundException("No search found with id <" + searchId + ">."));
}

public SearchJob executeAsync(String searchId, SearchUser searchUser, ExecutionState executionState) {
return searchDomain.getForUser(searchId, searchUser)
.map(s -> executeAsync(s, searchUser, executionState))
return executeAsync(searchId, searchUser, searchUser, searchUser, searchUser, searchUser, executionState);
}

public SearchJob executeAsync(String searchId, ViewPermissions viewPermissions, SearchPermissions searchPermissions, HasPermissions permissions, HasStreams streams, UserDetails name, ExecutionState executionState) {
return searchDomain.getForUser(searchId, viewPermissions, searchPermissions, name)
.map(s -> executeAsync(s, permissions, streams, name, executionState))
.orElseThrow(() -> new NotFoundException("No search found with id <" + searchId + ">."));
}

@WithSpan
public SearchJob executeSync(Search search, SearchUser searchUser, ExecutionState executionState) {
final SearchJob searchJob = prepareAndExecuteSearchJob(search, searchUser, executionState);
return this.executeSync(search, searchUser, searchUser, searchUser, executionState);
}

public SearchJob executeSync(Search search, HasPermissions permissions, HasStreams streams, UserDetails name, ExecutionState executionState) {
final SearchJob searchJob = prepareAndExecuteSearchJob(search, permissions, streams, name, executionState);

try {
final CompletableFuture<Void> resultFuture = searchJob.getResultFuture();
Expand Down Expand Up @@ -106,40 +119,46 @@ public SearchJob executeSync(Search search, SearchUser searchUser, ExecutionStat
}

@WithSpan
public SearchJob executeAsync(Search search, SearchUser searchUser, ExecutionState executionState) {
return prepareAndExecuteSearchJob(search, searchUser, executionState);
protected SearchJob executeAsync(Search search, HasPermissions permissions, HasStreams streams, UserDetails details, ExecutionState executionState) {
return prepareAndExecuteSearchJob(search, permissions, streams, details, executionState);
}

private SearchJob prepareAndExecuteSearchJob(final Search search,
final SearchUser searchUser,
HasPermissions permissions,
HasStreams streams,
UserDetails details,
final ExecutionState executionState) {
final Search preValidationSearch = searchNormalization.preValidation(search, searchUser, executionState);
final Search preValidationSearch = searchNormalization.preValidation(search, streams, executionState);

final Set<SearchError> validationErrors = searchValidation.validate(preValidationSearch, searchUser);
final Set<SearchError> validationErrors = searchValidation.validate(preValidationSearch, permissions);

if (hasFatalError(validationErrors)) {
return searchJobWithFatalError(searchJobService.create(preValidationSearch, searchUser.username()), validationErrors);
return searchJobWithFatalError(searchJobService.create(preValidationSearch, details.username()), validationErrors);
}

final Search normalizedSearch = searchNormalization.postValidation(preValidationSearch, searchUser, executionState);
final SearchJob searchJob = queryEngine.execute(searchJobService.create(normalizedSearch, searchUser.username()), validationErrors, searchUser.timeZone().orElse(DEFAULT_TIMEZONE));
final Search normalizedSearch = searchNormalization.postValidation(preValidationSearch, executionState);
final SearchJob searchJob = queryEngine.execute(searchJobService.create(normalizedSearch, details.username()), validationErrors, details.timeZone().orElse(DEFAULT_TIMEZONE));
validationErrors.forEach(searchJob::addError);
return searchJob;
}

public ExplainResults explain(String searchId, SearchUser searchUser, ExecutionState executionState) {
return searchDomain.getForUser(searchId, searchUser)
.map(s -> explain(s, searchUser, executionState))
return this.explain(searchId, searchUser, searchUser, searchUser, searchUser, searchUser, executionState);
}

public ExplainResults explain(String searchId, HasStreams searchUser, ViewPermissions viewPermissions, SearchPermissions searchPermissions, HasPermissions permissions, UserDetails name, ExecutionState executionState) {
return searchDomain.getForUser(searchId, viewPermissions, searchPermissions, name)
.map(s -> explain(s, searchUser, permissions, name, executionState))
.orElseThrow(() -> new NotFoundException("No search found with id <" + searchId + ">."));
}

public ExplainResults explain(Search search, SearchUser searchUser, ExecutionState executionState) {
public ExplainResults explain(Search search, HasStreams searchUser, HasPermissions permissions, UserDetails details, ExecutionState executionState) {
final Search preValidationSearch = searchNormalization.preValidation(search, searchUser, executionState);
final Set<SearchError> validationErrors = searchValidation.validate(preValidationSearch, searchUser);
final Search normalizedSearch = searchNormalization.postValidation(preValidationSearch, searchUser, executionState);
final Set<SearchError> validationErrors = searchValidation.validate(preValidationSearch, permissions);
final Search normalizedSearch = searchNormalization.postValidation(preValidationSearch, executionState);

return queryEngine.explain(searchJobService.create(normalizedSearch, searchUser.username()), validationErrors,
searchUser.timeZone().orElse(DEFAULT_TIMEZONE));
return queryEngine.explain(searchJobService.create(normalizedSearch, details.username()), validationErrors,
details.timeZone().orElse(DEFAULT_TIMEZONE));
}

private SearchJob searchJobWithFatalError(SearchJob searchJob, Set<SearchError> validationErrors) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.plugins.views.search.rest.ExecutionState;
import org.graylog.plugins.views.search.rest.ExecutionStateGlobalOverride;
import org.graylog.security.HasStreams;
import org.graylog2.plugin.Tools;
import org.joda.time.DateTime;

Expand Down Expand Up @@ -67,7 +68,7 @@ private Query normalize(final Query query,
}

@Override
public Search preValidation(Search search, SearchUser searchUser, ExecutionState executionState) {
public Search preValidation(Search search, HasStreams searchUser, ExecutionState executionState) {
final Search searchWithStreams = search.addStreamsToQueriesWithoutStreams(() -> searchUser.streams().loadMessageStreamsWithFallback());
final var now = referenceDateFromOverrideOrNow(executionState);
final var normalizedSearch = searchWithStreams.applyExecutionState(firstNonNull(executionState, ExecutionState.empty()))
Expand All @@ -84,12 +85,12 @@ private DateTime referenceDateFromOverrideOrNow(ExecutionState executionState) {
}

@Override
public Search postValidation(Search search, SearchUser searchUser, ExecutionState executionState) {
public Search postValidation(Search search, ExecutionState executionState) {
return normalize(search, postValidationNormalizers);
}

@Override
public Query preValidation(final Query query, final ParameterProvider parameterProvider, SearchUser searchUser, ExecutionState executionState) {
public Query preValidation(final Query query, final ParameterProvider parameterProvider, HasStreams searchUser, ExecutionState executionState) {
Query normalizedQuery = query;
if (!query.hasStreams()) {
normalizedQuery = query.addStreamsToFilter(searchUser.streams().loadMessageStreamsWithFallback());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
import org.graylog.plugins.views.search.Search;
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.plugins.views.search.rest.ExecutionState;
import org.graylog.security.HasStreams;

public interface SearchNormalization {
Search preValidation(Search search, SearchUser searchUser, ExecutionState executionState);
Search preValidation(Search search, HasStreams searchUser, ExecutionState executionState);

Search postValidation(Search search, SearchUser searchUser, ExecutionState executionState);
Search postValidation(Search search, ExecutionState executionState);

Query preValidation(final Query query, final ParameterProvider parameterProvider, SearchUser searchUser, ExecutionState executionState);
Query preValidation(final Query query, final ParameterProvider parameterProvider, HasStreams searchUser, ExecutionState executionState);

Query postValidation(final Query query, final ParameterProvider parameterProvider);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.graylog.plugins.views.search.SearchExecutionGuard;
import org.graylog.plugins.views.search.errors.SearchError;
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.plugins.views.search.permissions.StreamPermissions;
import org.graylog.security.HasPermissions;

import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -38,19 +40,19 @@ public PluggableSearchValidation(SearchExecutionGuard executionGuard,
}

@Override
public Set<SearchError> validate(final Search search, final SearchUser searchUser) {
this.executionGuard.check(search, searchUser::canReadStream);
public Set<SearchError> validate(final Search search, final HasPermissions permissions) {
this.executionGuard.check(search, permissions::canReadStream);

return this.pluggableSearchValidators.stream()
.flatMap(validator -> validator.validate(search, searchUser).stream())
.flatMap(validator -> validator.validate(search, permissions).stream())
.collect(Collectors.toSet());
}

@Override
public Set<SearchError> validate(final Query query, final SearchUser searchUser) {
this.executionGuard.checkUserIsPermittedToSeeStreams(query.streamIdsForPermissionsCheck(), searchUser::canReadStream);
public Set<SearchError> validate(final Query query, final HasPermissions permissions) {
this.executionGuard.checkUserIsPermittedToSeeStreams(query.streamIdsForPermissionsCheck(), permissions::canReadStream);
return this.pluggableSearchValidators.stream()
.flatMap(validator -> validator.validate(query, searchUser).stream())
.flatMap(validator -> validator.validate(query, permissions).stream())
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
import org.graylog.plugins.views.search.Search;
import org.graylog.plugins.views.search.errors.SearchError;
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.security.HasPermissions;

import java.util.Set;

public interface SearchValidation {
Set<SearchError> validate(final Search search, final SearchUser searchUser);
Set<SearchError> validate(final Search search, final HasPermissions searchUser);

Set<SearchError> validate(final Query query, final SearchUser searchUser);
Set<SearchError> validate(final Query query, final HasPermissions searchUser);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
import org.graylog.plugins.views.search.Search;
import org.graylog.plugins.views.search.errors.SearchError;
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.security.HasPermissions;

import java.util.Set;

public interface SearchValidator {
Set<SearchError> validate(final Search search, final SearchUser searchUser);
Set<SearchError> validate(final Search search, final HasPermissions searchUser);

Set<SearchError> validate(final Query query, final SearchUser searchUser);
Set<SearchError> validate(final Query query, final HasPermissions searchUser);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.graylog.plugins.views.search.errors.SearchError;
import org.graylog.plugins.views.search.errors.SearchTypeError;
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.security.HasPermissions;
import org.graylog2.plugin.indexer.searches.timeranges.TimeRange;
import org.joda.time.DateTime;
import org.joda.time.Period;
Expand Down Expand Up @@ -72,7 +73,7 @@ boolean isOutOfLimit(TimeRange timeRange, Period limit) {
}

@Override
public Set<SearchError> validate(final Search search, final SearchUser searchUser) {
public Set<SearchError> validate(final Search search, final HasPermissions searchUser) {
final SearchConfig searchConfig = searchConfigProvider.get();
return search.queries()
.stream()
Expand All @@ -81,7 +82,7 @@ public Set<SearchError> validate(final Search search, final SearchUser searchUse
}

@Override
public Set<SearchError> validate(final Query query, final SearchUser searchUser) {
public Set<SearchError> validate(final Query query, final HasPermissions searchUser) {
final SearchConfig searchConfig = searchConfigProvider.get();
return validateQueryTimeRange(query, searchConfig).collect(Collectors.toSet());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
import org.graylog.plugins.views.search.views.ViewLike;
import org.graylog.plugins.views.search.views.ViewResolver;
import org.graylog.plugins.views.search.views.ViewResolverDecoder;
import org.graylog.security.HasPermissions;
import org.graylog.security.UserDetails;
import org.graylog.security.HasStreams;
import org.graylog.security.HasUser;
import org.graylog2.database.DbEntity;
import org.graylog2.plugin.database.users.User;
Expand All @@ -37,7 +40,7 @@
import java.util.function.BiPredicate;
import java.util.function.Predicate;

public class SearchUser implements SearchPermissions, StreamPermissions, ViewPermissions, EntityPermissions, HasUser {
public class SearchUser implements SearchPermissions, StreamPermissions, ViewPermissions, EntityPermissions, HasUser, HasStreams, HasPermissions, UserDetails {
private static final Logger LOG = LoggerFactory.getLogger(SearchUser.class);
private final User currentUser;
private final Predicate<String> isPermitted;
Expand All @@ -54,10 +57,12 @@ public SearchUser(User currentUser, Predicate<String> isPermitted, BiPredicate<S
this.viewResolvers = viewResolvers;
}

@Override
public Optional<DateTimeZone> timeZone() {
return Optional.ofNullable(this.currentUser.getTimeZone());
}

@Override
public String username() {
return this.currentUser.getName();
}
Expand Down Expand Up @@ -111,10 +116,12 @@ public boolean canReadTitle(String readPermission, String idAsString) {
return DbEntity.ALL_ALLOWED.equals(readPermission) || isPermitted(readPermission, idAsString);
}

@Override
public boolean isPermitted(String permission) {
return this.isPermitted.test(permission);
}

@Override
public boolean isPermitted(String permission, String entityId) {
return this.isPermittedEntity.test(permission, entityId);
}
Expand All @@ -128,6 +135,7 @@ public boolean isAdmin() {
return this.currentUser.isLocalAdmin() || isPermitted("*");
}

@Override
public UserStreams streams() {
return this.userStreams;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
package org.graylog.security;

import org.graylog.plugins.views.search.permissions.StreamPermissions;

public interface HasPermissions extends StreamPermissions {
boolean isPermitted(String permission);

boolean isPermitted(String permission, String entityId);
}