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 b7cefa3fcd85..348c40a58ab4 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 @@ -22,10 +22,9 @@ import com.google.common.base.Function; import com.google.common.base.Optional; 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.Maps; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.sun.jersey.spi.container.ResourceFilters; @@ -61,6 +60,20 @@ @Path("/druid/indexer/v1/supervisor") public class SupervisorResource { + private static final Function> SPEC_DATASOURCE_READ_RA_GENERATOR = + supervisorSpec -> { + if (supervisorSpec.getSpec() == null) { + return null; + } + if (supervisorSpec.getSpec().getDataSources() == null) { + return null; + } + return Iterables.transform( + supervisorSpec.getSpec().getDataSources(), + AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR + ); + }; + private final TaskMaster taskMaster; private final AuthConfig authConfig; private final AuthorizerMapper authorizerMapper; @@ -216,26 +229,14 @@ public Response specGetAllHistory(@Context final HttpServletRequest req) @Override public Response apply(final SupervisorManager manager) { - 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) - { - return authorizedSupervisorIds.contains(id); - } - } - ); - return Response.ok(authorizedSupervisorHistory).build(); + return Response.ok( + AuthorizationUtils.filterAuthorizedResources( + req, + manager.getSupervisorHistory(), + SPEC_DATASOURCE_READ_RA_GENERATOR, + authorizerMapper + ) + ).build(); } } ); @@ -244,8 +245,9 @@ public boolean apply(String id) @GET @Path("/{id}/history") @Produces(MediaType.APPLICATION_JSON) - @ResourceFilters(SupervisorResourceFilter.class) - public Response specGetHistory(@PathParam("id") final String id) + public Response specGetHistory( + @Context final HttpServletRequest req, + @PathParam("id") final String id) { return asLeaderWithSupervisorManager( new Function() @@ -253,23 +255,32 @@ public Response specGetHistory(@PathParam("id") final String id) @Override public Response apply(SupervisorManager manager) { - Map> history = manager.getSupervisorHistory(); - if (history.containsKey(id)) { - return Response.ok(history.get(id)).build(); - } else { - return Response.status(Response.Status.NOT_FOUND) - .entity( - ImmutableMap.of( - "error", - StringUtils.format( - "No history for [%s] (history available for %s)", - id, - history.keySet() - ) - ) - ) - .build(); + Map> supervisorHistory = manager.getSupervisorHistory(); + Iterable historyForId = supervisorHistory.get(id); + if (historyForId != null) { + final List authorizedHistoryForId = + Lists.newArrayList( + AuthorizationUtils.filterAuthorizedResources( + req, + historyForId, + SPEC_DATASOURCE_READ_RA_GENERATOR, + authorizerMapper + ) + ); + if (authorizedHistoryForId.size() > 0) { + return Response.ok(authorizedHistoryForId).build(); + } } + + return Response.status(Response.Status.NOT_FOUND) + .entity( + ImmutableMap.of( + "error", + StringUtils.format("No history for [%s].", id) + ) + ) + .build(); + } } ); diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/supervisor/SupervisorResourceTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/supervisor/SupervisorResourceTest.java index 112a2617ade1..f7707cc76456 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/supervisor/SupervisorResourceTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/supervisor/SupervisorResourceTest.java @@ -47,6 +47,7 @@ import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.Response; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; @@ -101,7 +102,7 @@ public Access authorize( @Test public void testSpecPost() throws Exception { - SupervisorSpec spec = new TestSupervisorSpec("my-id", null) { + SupervisorSpec spec = new TestSupervisorSpec("my-id", null, null) { @Override public List getDataSources() @@ -140,7 +141,7 @@ public List getDataSources() public void testSpecGetAll() throws Exception { Set supervisorIds = ImmutableSet.of("id1", "id2"); - SupervisorSpec spec1 = new TestSupervisorSpec("id1", null) { + SupervisorSpec spec1 = new TestSupervisorSpec("id1", null, null) { @Override public List getDataSources() @@ -148,7 +149,7 @@ public List getDataSources() return Lists.newArrayList("datasource1"); } }; - SupervisorSpec spec2 = new TestSupervisorSpec("id2", null) { + SupervisorSpec spec2 = new TestSupervisorSpec("id2", null, null) { @Override public List getDataSources() @@ -188,7 +189,7 @@ public List getDataSources() @Test public void testSpecGet() throws Exception { - SupervisorSpec spec = new TestSupervisorSpec("my-id", null); + SupervisorSpec spec = new TestSupervisorSpec("my-id", null, null); EasyMock.expect(taskMaster.getSupervisorManager()).andReturn(Optional.of(supervisorManager)).times(2); EasyMock.expect(supervisorManager.getSupervisorSpec("my-id")).andReturn(Optional.of(spec)); @@ -279,28 +280,34 @@ public void testShutdown() throws Exception @Test public void testSpecGetAllHistory() throws Exception { + List versions1 = ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")), + "v1" + ), + new VersionedSupervisorSpec( + new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")), + "v2" + ) + ); + List versions2 = ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")), + "v1" + ), + new VersionedSupervisorSpec( + new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")), + "v2" + ) + ); Map> history = Maps.newHashMap(); - history.put("id1", null); - history.put("id2", null); + history.put("id1", versions1); + history.put("id2", versions2); EasyMock.expect(taskMaster.getSupervisorManager()).andReturn(Optional.of(supervisorManager)).times(2); EasyMock.expect(supervisorManager.getSupervisorHistory()).andReturn(history); - SupervisorSpec spec1 = new TestSupervisorSpec("id1", null) { - - @Override - public List getDataSources() - { - return Lists.newArrayList("datasource1"); - } - }; - SupervisorSpec spec2 = new TestSupervisorSpec("id2", null) { - - @Override - public List getDataSources() - { - return Lists.newArrayList("datasource2"); - } - }; + SupervisorSpec spec1 = new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")); + SupervisorSpec spec2 = new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")); EasyMock.expect(supervisorManager.getSupervisorSpec("id1")).andReturn(Optional.of(spec1)).atLeastOnce(); EasyMock.expect(supervisorManager.getSupervisorSpec("id2")).andReturn(Optional.of(spec2)).atLeastOnce(); EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce(); @@ -330,28 +337,35 @@ public List getDataSources() @Test public void testSpecGetAllHistoryWithAuthFailureFiltering() throws Exception { + List versions1 = ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")), + "v1" + ), + new VersionedSupervisorSpec( + new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")), + "v2" + ) + ); + List versions2 = ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")), + "v1" + ), + new VersionedSupervisorSpec( + new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")), + "v2" + ) + ); + Map> history = Maps.newHashMap(); - history.put("id1", null); - history.put("id2", null); + history.put("id1", versions1); + history.put("id2", versions2); EasyMock.expect(taskMaster.getSupervisorManager()).andReturn(Optional.of(supervisorManager)).times(2); EasyMock.expect(supervisorManager.getSupervisorHistory()).andReturn(history); - SupervisorSpec spec1 = new TestSupervisorSpec("id1", null) { - - @Override - public List getDataSources() - { - return Lists.newArrayList("datasource1"); - } - }; - SupervisorSpec spec2 = new TestSupervisorSpec("id2", null) { - - @Override - public List getDataSources() - { - return Lists.newArrayList("datasource2"); - } - }; + SupervisorSpec spec1 = new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")); + SupervisorSpec spec2 = new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")); EasyMock.expect(supervisorManager.getSupervisorSpec("id1")).andReturn(Optional.of(spec1)).atLeastOnce(); EasyMock.expect(supervisorManager.getSupervisorSpec("id2")).andReturn(Optional.of(spec2)).atLeastOnce(); EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce(); @@ -365,7 +379,7 @@ public List getDataSources() Response response = supervisorResource.specGetAllHistory(request); Map> filteredHistory = Maps.newHashMap(); - filteredHistory.put("id1", null); + filteredHistory.put("id1", versions1); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(filteredHistory, response.getEntity()); @@ -384,24 +398,51 @@ public List getDataSources() @Test public void testSpecGetHistory() throws Exception { - List versions = ImmutableList.of( - new VersionedSupervisorSpec(null, "v1"), - new VersionedSupervisorSpec(null, "v2") + List versions1 = ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")), + "v1" + ), + new VersionedSupervisorSpec( + new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")), + "v2" + ) + ); + List versions2 = ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")), + "v1" + ), + new VersionedSupervisorSpec( + new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")), + "v2" + ) ); Map> history = Maps.newHashMap(); - history.put("id1", versions); - history.put("id2", null); + history.put("id1", versions1); + history.put("id2", versions2); - EasyMock.expect(taskMaster.getSupervisorManager()).andReturn(Optional.of(supervisorManager)).times(2); - EasyMock.expect(supervisorManager.getSupervisorHistory()).andReturn(history).times(2); + EasyMock.expect(taskMaster.getSupervisorManager()).andReturn(Optional.of(supervisorManager)).times(3); + EasyMock.expect(supervisorManager.getSupervisorHistory()).andReturn(history).times(3); + EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce(); + EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn( + new AuthenticationResult("druid", "druid", null) + ).atLeastOnce(); + request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true); + EasyMock.expectLastCall().anyTimes(); replayAll(); - Response response = supervisorResource.specGetHistory("id1"); + Response response = supervisorResource.specGetHistory(request, "id1"); + + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(versions1, response.getEntity()); + + response = supervisorResource.specGetHistory(request, "id2"); Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals(versions, response.getEntity()); + Assert.assertEquals(versions2, response.getEntity()); - response = supervisorResource.specGetHistory("id3"); + response = supervisorResource.specGetHistory(request, "id3"); Assert.assertEquals(404, response.getStatus()); @@ -410,7 +451,92 @@ public void testSpecGetHistory() throws Exception EasyMock.expect(taskMaster.getSupervisorManager()).andReturn(Optional.absent()); replayAll(); - response = supervisorResource.specGetHistory("id1"); + response = supervisorResource.specGetHistory(request, "id1"); + verifyAll(); + + Assert.assertEquals(503, response.getStatus()); + } + + @Test + public void testSpecGetHistoryWithAuthFailure() throws Exception + { + List versions1 = ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")), + "v1" + ), + new VersionedSupervisorSpec( + new TestSupervisorSpec("id1", null, Arrays.asList("datasource1")), + "v2" + ) + ); + List versions2 = ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")), + "v1" + ), + new VersionedSupervisorSpec( + new TestSupervisorSpec("id2", null, Arrays.asList("datasource2")), + "v2" + ) + ); + List versions3 = ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id3", null, Arrays.asList("datasource3")), + "v1" + ), + new VersionedSupervisorSpec( + new TestSupervisorSpec("id3", null, Arrays.asList("datasource2")), + "v2" + ) + ); + Map> history = Maps.newHashMap(); + history.put("id1", versions1); + history.put("id2", versions2); + history.put("id3", versions3); + + EasyMock.expect(taskMaster.getSupervisorManager()).andReturn(Optional.of(supervisorManager)).times(4); + EasyMock.expect(supervisorManager.getSupervisorHistory()).andReturn(history).times(4); + EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce(); + EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn( + new AuthenticationResult("notdruid", "druid", null) + ).atLeastOnce(); + request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true); + EasyMock.expectLastCall().anyTimes(); + replayAll(); + + Response response = supervisorResource.specGetHistory(request, "id1"); + + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(versions1, response.getEntity()); + + response = supervisorResource.specGetHistory(request, "id2"); + + // user is not authorized to access datasource2 + Assert.assertEquals(404, response.getStatus()); + + response = supervisorResource.specGetHistory(request, "id3"); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals( + ImmutableList.of( + new VersionedSupervisorSpec( + new TestSupervisorSpec("id3", null, Arrays.asList("datasource3")), + "v1" + ) + ), + response.getEntity() + ); + + response = supervisorResource.specGetHistory(request, "id4"); + Assert.assertEquals(404, response.getStatus()); + + + resetAll(); + + EasyMock.expect(taskMaster.getSupervisorManager()).andReturn(Optional.absent()); + replayAll(); + + response = supervisorResource.specGetHistory(request, "id1"); verifyAll(); Assert.assertEquals(503, response.getStatus()); @@ -453,11 +579,13 @@ private static class TestSupervisorSpec implements SupervisorSpec { private final String id; private final Supervisor supervisor; + private final List datasources; - public TestSupervisorSpec(String id, Supervisor supervisor) + public TestSupervisorSpec(String id, Supervisor supervisor, List datasources) { this.id = id; this.supervisor = supervisor; + this.datasources = datasources; } @Override @@ -475,7 +603,38 @@ public Supervisor createSupervisor() @Override public List getDataSources() { - return null; + return datasources; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TestSupervisorSpec that = (TestSupervisorSpec) o; + + if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) { + return false; + } + if (supervisor != null ? !supervisor.equals(that.supervisor) : that.supervisor != null) { + return false; + } + return datasources != null ? datasources.equals(that.datasources) : that.datasources == null; + + } + + @Override + public int hashCode() + { + int result = getId() != null ? getId().hashCode() : 0; + result = 31 * result + (supervisor != null ? supervisor.hashCode() : 0); + result = 31 * result + (datasources != null ? datasources.hashCode() : 0); + return result; } } } diff --git a/server/src/main/java/io/druid/indexing/overlord/supervisor/VersionedSupervisorSpec.java b/server/src/main/java/io/druid/indexing/overlord/supervisor/VersionedSupervisorSpec.java index b96edf37406d..93772486c3a6 100644 --- a/server/src/main/java/io/druid/indexing/overlord/supervisor/VersionedSupervisorSpec.java +++ b/server/src/main/java/io/druid/indexing/overlord/supervisor/VersionedSupervisorSpec.java @@ -45,4 +45,31 @@ public String getVersion() { return version; } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + VersionedSupervisorSpec that = (VersionedSupervisorSpec) o; + + if (getSpec() != null ? !getSpec().equals(that.getSpec()) : that.getSpec() != null) { + return false; + } + return getVersion() != null ? getVersion().equals(that.getVersion()) : that.getVersion() == null; + + } + + @Override + public int hashCode() + { + int result = getSpec() != null ? getSpec().hashCode() : 0; + result = 31 * result + (getVersion() != null ? getVersion().hashCode() : 0); + return result; + } } 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 68e35b5484c2..b14d6a788511 100644 --- a/server/src/main/java/io/druid/server/security/AuthorizationUtils.java +++ b/server/src/main/java/io/druid/server/security/AuthorizationUtils.java @@ -27,6 +27,7 @@ import io.druid.java.util.common.ISE; import javax.servlet.http.HttpServletRequest; +import java.util.List; import java.util.Map; import java.util.Set; @@ -281,6 +282,65 @@ public static Iterable filterAuthorizedResources( return filteredResources; } + /** + * Given a map of resource lists, filter each resources list by applying the resource action generator to each + * item in each resource list. + * + * The resourceActionGenerator returns an Iterable for each resource. + * + * If a resource list is null or has no authorized items after filtering, it will not be included in the returned + * map. + * + * 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 unfilteredResources Map of resource lists to be filtered + * @param resourceActionGenerator Function that creates an iterable of resource-actions from a resource + * @param authorizerMapper authorizer mapper + * + * @return Map containing lists of resources that were authorized + */ + public static Map> filterAuthorizedResources( + final HttpServletRequest request, + final Map> unfilteredResources, + 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 = AuthorizationUtils.authenticationResultFromRequest(request); + + Map> filteredResources = Maps.newHashMap(); + for (Map.Entry> entry : unfilteredResources.entrySet()) { + if (entry.getValue() == null) { + continue; + } + + final List filteredList = Lists.newArrayList( + AuthorizationUtils.filterAuthorizedResources( + authenticationResult, + entry.getValue(), + resourceActionGenerator, + authorizerMapper + ) + ); + + if (filteredList.size() > 0) { + filteredResources.put( + entry.getKey(), + filteredList + ); + } + } + + 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