diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java index d8ef4bc8701..d87faa91072 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java @@ -365,24 +365,22 @@ public class OverlordResource // A bit roundabout, but works as a way of figuring out what tasks haven't been handed // off to the runner yet: final List allActiveTasks = taskStorageQueryAdapter.getActiveTasks(); - final List activeTasks; - Function raGenerator = new Function() - { - @Override - public ResourceAction apply(Task input) - { - return new ResourceAction( - new Resource(input.getDataSource(), ResourceType.DATASOURCE), - Action.READ - ); - } + Function> raGenerator = task -> { + return Lists.newArrayList( + new ResourceAction( + new Resource(task.getDataSource(), ResourceType.DATASOURCE), + Action.READ + ) + ); }; - activeTasks = AuthorizationUtils.filterAuthorizedResources( - req, - allActiveTasks, - raGenerator, - authorizerMapper + final List activeTasks = Lists.newArrayList( + AuthorizationUtils.filterAuthorizedResources( + req, + allActiveTasks, + raGenerator, + authorizerMapper + ) ); final Set runnersKnownTasks = Sets.newHashSet( @@ -464,34 +462,32 @@ public class OverlordResource @Produces(MediaType.APPLICATION_JSON) public Response getCompleteTasks(@Context final HttpServletRequest req) { - final List recentlyFinishedTasks; - Function raGenerator = new Function() - { - @Override - public ResourceAction apply(TaskStatus input) - { - final String taskId = input.getId(); - final Optional optionalTask = taskStorageQueryAdapter.getTask(taskId); - if (!optionalTask.isPresent()) { - throw new WebApplicationException( - Response.serverError().entity( - StringUtils.format("No task information found for task with id: [%s]", taskId) - ).build() - ); - } - - return new ResourceAction( - new Resource(optionalTask.get().getDataSource(), ResourceType.DATASOURCE), - Action.READ + Function> raGenerator = taskStatus -> { + final String taskId = taskStatus.getId(); + final Optional optionalTask = taskStorageQueryAdapter.getTask(taskId); + if (!optionalTask.isPresent()) { + throw new WebApplicationException( + Response.serverError().entity( + StringUtils.format("No task information found for task with id: [%s]", taskId) + ).build() ); } + + return Lists.newArrayList( + new ResourceAction( + new Resource(optionalTask.get().getDataSource(), ResourceType.DATASOURCE), + Action.READ + ) + ); }; - recentlyFinishedTasks = AuthorizationUtils.filterAuthorizedResources( - req, - taskStorageQueryAdapter.getRecentlyFinishedTaskStatuses(), - raGenerator, - authorizerMapper + final List recentlyFinishedTasks = Lists.newArrayList( + AuthorizationUtils.filterAuthorizedResources( + req, + taskStorageQueryAdapter.getRecentlyFinishedTaskStatuses(), + raGenerator, + authorizerMapper + ) ); final List completeTasks = Lists.transform( @@ -648,33 +644,32 @@ public class OverlordResource HttpServletRequest req ) { - Function raGenerator = new Function() - { - @Override - public ResourceAction apply(TaskRunnerWorkItem input) - { - final String taskId = input.getTaskId(); - final Optional optionalTask = taskStorageQueryAdapter.getTask(taskId); - if (!optionalTask.isPresent()) { - throw new WebApplicationException( - Response.serverError().entity( - StringUtils.format("No task information found for task with id: [%s]", taskId) - ).build() - ); - } - - return new ResourceAction( - new Resource(optionalTask.get().getDataSource(), ResourceType.DATASOURCE), - Action.READ + Function> raGenerator = taskRunnerWorkItem -> { + final String taskId = taskRunnerWorkItem.getTaskId(); + final Optional optionalTask = taskStorageQueryAdapter.getTask(taskId); + if (!optionalTask.isPresent()) { + throw new WebApplicationException( + Response.serverError().entity( + StringUtils.format("No task information found for task with id: [%s]", taskId) + ).build() ); } + + return Lists.newArrayList( + new ResourceAction( + new Resource(optionalTask.get().getDataSource(), ResourceType.DATASOURCE), + Action.READ + ) + ); }; - return AuthorizationUtils.filterAuthorizedResources( - req, - collectionToFilter, - raGenerator, - authorizerMapper + return Lists.newArrayList( + AuthorizationUtils.filterAuthorizedResources( + req, + collectionToFilter, + raGenerator, + authorizerMapper + ) ); } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/supervisor/SupervisorResource.java b/indexing-service/src/main/java/io/druid/indexing/overlord/supervisor/SupervisorResource.java index ec3aad39bf0..c23e028e6f1 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/supervisor/SupervisorResource.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/supervisor/SupervisorResource.java @@ -25,7 +25,6 @@ import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.inject.Inject; @@ -38,6 +37,7 @@ import io.druid.server.security.AuthConfig; import io.druid.server.security.AuthorizerMapper; import io.druid.server.security.AuthorizationUtils; import io.druid.server.security.ForbiddenException; +import io.druid.server.security.ResourceAction; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; @@ -49,6 +49,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -119,36 +120,12 @@ public class SupervisorResource @Override public Response apply(final SupervisorManager manager) { - final Set supervisorIds; - supervisorIds = Sets.newHashSet(); - for (String supervisorId : manager.getSupervisorIds()) { - Optional supervisorSpecOptional = manager.getSupervisorSpec(supervisorId); - if (supervisorSpecOptional.isPresent()) { - Access accessResult = AuthorizationUtils.authorizeAllResourceActions( - req, - Iterables.transform( - supervisorSpecOptional.get().getDataSources(), - AuthorizationUtils.DATASOURCE_WRITE_RA_GENERATOR - ), - authorizerMapper - ); - - if (accessResult.isAllowed()) { - supervisorIds.add(supervisorId); - } - } - } - - // If there were no supervisorIds, go ahead and authorize the request. - if (manager.getSupervisorIds().size() == 0) { - AuthorizationUtils.authorizeAllResourceActions( - req, - Lists.newArrayList(), - authorizerMapper - ); - } - - return Response.ok(supervisorIds).build(); + Set authorizedSupervisorIds = filterAuthorizedSupervisorIds( + req, + manager, + manager.getSupervisorIds() + ); + return Response.ok(authorizedSupervisorIds).build(); } } ); @@ -239,27 +216,22 @@ public class SupervisorResource @Override public Response apply(final SupervisorManager manager) { - final Map> supervisorHistory; - supervisorHistory = Maps.filterKeys( - manager.getSupervisorHistory(), + final Map> supervisorHistory = manager.getSupervisorHistory(); + + final Set authorizedSupervisorIds = filterAuthorizedSupervisorIds( + req, + manager, + supervisorHistory.keySet() + ); + + final Map> authorizedSupervisorHistory = Maps.filterKeys( + supervisorHistory, new Predicate() { @Override public boolean apply(String id) { - Optional supervisorSpecOptional = manager.getSupervisorSpec(id); - if (!supervisorSpecOptional.isPresent()) { - return false; - } - Access accessResult = AuthorizationUtils.authorizeAllResourceActions( - req, - Iterables.transform( - supervisorSpecOptional.get().getDataSources(), - AuthorizationUtils.DATASOURCE_WRITE_RA_GENERATOR - ), - authorizerMapper - ); - return accessResult.isAllowed(); + return authorizedSupervisorIds.contains(id); } } ); @@ -337,4 +309,32 @@ public class SupervisorResource return Response.status(Response.Status.SERVICE_UNAVAILABLE).build(); } } + + private Set filterAuthorizedSupervisorIds( + final HttpServletRequest req, + SupervisorManager manager, + Collection supervisorIds + ) + { + Function> raGenerator = supervisorId -> { + Optional supervisorSpecOptional = manager.getSupervisorSpec(supervisorId); + if (supervisorSpecOptional.isPresent()) { + return Iterables.transform( + supervisorSpecOptional.get().getDataSources(), + AuthorizationUtils.DATASOURCE_WRITE_RA_GENERATOR + ); + } else { + return null; + } + }; + + return Sets.newHashSet( + AuthorizationUtils.filterAuthorizedResources( + req, + supervisorIds, + raGenerator, + authorizerMapper + ) + ); + } } diff --git a/server/src/main/java/io/druid/server/ClientInfoResource.java b/server/src/main/java/io/druid/server/ClientInfoResource.java index ff46da0be0d..20ecaee6306 100644 --- a/server/src/main/java/io/druid/server/ClientInfoResource.java +++ b/server/src/main/java/io/druid/server/ClientInfoResource.java @@ -19,6 +19,7 @@ package io.druid.server; +import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -43,6 +44,7 @@ import io.druid.server.http.security.DatasourceResourceFilter; import io.druid.server.security.AuthConfig; import io.druid.server.security.AuthorizerMapper; import io.druid.server.security.AuthorizationUtils; +import io.druid.server.security.ResourceAction; import io.druid.timeline.DataSegment; import io.druid.timeline.TimelineLookup; import io.druid.timeline.TimelineObjectHolder; @@ -119,10 +121,14 @@ public class ClientInfoResource @Produces(MediaType.APPLICATION_JSON) public Iterable getDataSources(@Context final HttpServletRequest request) { + Function> raGenerator = datasourceName -> { + return Lists.newArrayList(AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR.apply(datasourceName)); + }; + return AuthorizationUtils.filterAuthorizedResources( request, getSegmentsForDatasources().keySet(), - AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR, + raGenerator, authorizerMapper ); } diff --git a/server/src/main/java/io/druid/server/http/MetadataResource.java b/server/src/main/java/io/druid/server/http/MetadataResource.java index fc1fadd11ac..5d1e6c74bdb 100644 --- a/server/src/main/java/io/druid/server/http/MetadataResource.java +++ b/server/src/main/java/io/druid/server/http/MetadataResource.java @@ -23,6 +23,7 @@ import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.sun.jersey.spi.container.ResourceFilters; @@ -33,6 +34,7 @@ import io.druid.server.http.security.DatasourceResourceFilter; import io.druid.server.security.AuthConfig; import io.druid.server.security.AuthorizerMapper; import io.druid.server.security.AuthorizationUtils; +import io.druid.server.security.ResourceAction; import io.druid.timeline.DataSegment; import org.joda.time.Interval; @@ -102,14 +104,20 @@ public class MetadataResource ); } - List datasourceNamesList = AuthorizationUtils.filterAuthorizedResources( - req, - dataSourceNamesPreAuth, - AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR, - authorizerMapper - ); + final Set dataSourceNamesPostAuth = Sets.newTreeSet(); + Function> raGenerator = datasourceName -> { + return Lists.newArrayList(AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR.apply(datasourceName)); + }; - final Set dataSourceNamesPostAuth = Sets.newTreeSet(datasourceNamesList); + Iterables.addAll( + dataSourceNamesPostAuth, + AuthorizationUtils.filterAuthorizedResources( + req, + dataSourceNamesPreAuth, + raGenerator, + authorizerMapper + ) + ); // Cannot do both includeDisabled and full, let includeDisabled take priority // Always use dataSourceNamesPostAuth to determine the set of returned dataSources diff --git a/server/src/main/java/io/druid/server/security/AuthorizationUtils.java b/server/src/main/java/io/druid/server/security/AuthorizationUtils.java index 5c8fb2e4136..b25da17a5a7 100644 --- a/server/src/main/java/io/druid/server/security/AuthorizationUtils.java +++ b/server/src/main/java/io/druid/server/security/AuthorizationUtils.java @@ -20,15 +20,13 @@ package io.druid.server.security; import com.google.common.base.Function; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import io.druid.java.util.common.ISE; import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.Set; @@ -153,25 +151,41 @@ public class AuthorizationUtils } /** - * Filter a list of resource-actions using the request's authorization fields, returning a new list of - * resource-actions that were authorized. + * Filter a collection of resources by applying the resourceActionGenerator to each resource, return an iterable + * containing the filtered resources. + * + * The resourceActionGenerator returns an Iterable for each resource. + * + * If every resource-action in the iterable is authorized, the resource will be added to the filtered resources. + * + * If there is an authorization failure for one of the resource-actions, the resource will not be + * added to the returned filtered resources.. + * + * If the resourceActionGenerator returns null for a resource, that resource will not be added to the filtered + * resources. * * This function will set the DRUID_AUTHORIZATION_CHECKED attribute in the request. * * If this attribute is already set when this function is called, an exception is thrown. * * @param request HTTP request to be authorized - * @param resources List of resources to be processed into resource-actions - * @param resourceActionGenerator Function that creates a resource-action from a resource - * @return A list containing the resource-actions from the resourceParser that were successfully authorized. + * @param resources resources to be processed into resource-actions + * @param resourceActionGenerator Function that creates an iterable of resource-actions from a resource + * @param authorizerMapper authorizer mapper + * @return Iterable containing resources that were authorized + * */ - public static List filterAuthorizedResources( + public static Iterable filterAuthorizedResources( final HttpServletRequest request, - final Collection resources, - final Function resourceActionGenerator, + final Iterable resources, + final Function> resourceActionGenerator, final AuthorizerMapper authorizerMapper ) { + if (request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED) != null) { + throw new ISE("Request already had authorization check."); + } + final AuthenticationResult authenticationResult = (AuthenticationResult) request.getAttribute( AuthConfig.DRUID_AUTHENTICATION_RESULT ); @@ -185,28 +199,39 @@ public class AuthorizationUtils } final Map resultCache = Maps.newHashMap(); - List filteredResources = new ArrayList<>(); - for (ResType resource : resources) { - final ResourceAction resourceAction = resourceActionGenerator.apply(resource); - Access access = resultCache.computeIfAbsent( - resourceAction, - ra -> authorizer.authorize( - authenticationResult, - ra.getResource(), - ra.getAction() - ) - ); - if (access.isAllowed()) { - filteredResources.add(resource); - } - } + final Iterable filteredResources = Iterables.filter( + resources, + resource -> { + final Iterable resourceActions = resourceActionGenerator.apply(resource); + if (resourceActions == null) { + return false; + } + for (ResourceAction resourceAction : resourceActions) { + Access access = resultCache.computeIfAbsent( + resourceAction, + ra -> authorizer.authorize( + authenticationResult, + ra.getResource(), + ra.getAction() + ) + ); + if (!access.isAllowed()) { + return false; + } + } + return true; + } + ); // We're filtering, so having access to none of the objects isn't an authorization failure (in terms of whether // to send an error response or not.) request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true); + return filteredResources; } + + /** * Function for the common pattern of generating a resource-action for reading from a datasource, using the * datasource name.