Repository: ambari Updated Branches: refs/heads/trunk fa0893684 -> dc0722265
AMBARI-18023. Enforce granular role-based access control for log search functions (rlevas) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/dc072226 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/dc072226 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/dc072226 Branch: refs/heads/trunk Commit: dc0722265bb7f6438807bafa8c9edbb96ab50559 Parents: fa08936 Author: Robert Levas <[email protected]> Authored: Fri Aug 5 11:14:17 2016 -0400 Committer: Robert Levas <[email protected]> Committed: Fri Aug 5 11:14:17 2016 -0400 ---------------------------------------------------------------------- .../server/api/services/LoggingService.java | 56 ++- .../logging/LoggingSearchPropertyProvider.java | 53 +++ .../server/api/services/LoggingServiceTest.java | 90 ++++- .../LoggingSearchPropertyProviderTest.java | 405 +++++++++++++------ .../security/TestAuthenticationFactory.java | 3 + 5 files changed, 461 insertions(+), 146 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/dc072226/ambari-server/src/main/java/org/apache/ambari/server/api/services/LoggingService.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/LoggingService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/LoggingService.java index 91123b5..bfa6975 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/LoggingService.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/LoggingService.java @@ -18,6 +18,7 @@ package org.apache.ambari.server.api.services; +import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.api.services.serializers.ResultSerializer; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.AmbariServer; @@ -27,7 +28,13 @@ import org.apache.ambari.server.controller.logging.LoggingRequestHelper; import org.apache.ambari.server.controller.logging.LoggingRequestHelperFactory; import org.apache.ambari.server.controller.logging.LoggingRequestHelperFactoryImpl; import org.apache.ambari.server.controller.spi.Resource; +import org.apache.ambari.server.security.authorization.AuthorizationException; +import org.apache.ambari.server.security.authorization.AuthorizationHelper; +import org.apache.ambari.server.security.authorization.ResourceType; +import org.apache.ambari.server.security.authorization.RoleAuthorization; +import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.utils.RetryHelper; +import org.apache.commons.lang.StringUtils; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -38,9 +45,11 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * This Service provides access to the LogSearch query services, including: @@ -51,6 +60,11 @@ import java.util.Map; */ public class LoggingService extends BaseService { + /** + * The user of authorizations for which a user must have one of in order to access LogSearch data + */ + private static final Set<RoleAuthorization> REQUIRED_AUTHORIZATIONS = EnumSet.of(RoleAuthorization.SERVICE_VIEW_OPERATIONAL_LOGS); + private final ControllerFactory controllerFactory; private final LoggingRequestHelperFactory helperFactory; @@ -71,8 +85,46 @@ public class LoggingService extends BaseService { @GET @Path("searchEngine") @Produces("text/plain") - public Response getSearchEngine(String body, @Context HttpHeaders headers, @Context UriInfo uri) { - return handleDirectRequest(uri, MediaType.TEXT_PLAIN_TYPE); + public Response getSearchEngine(String body, @Context HttpHeaders headers, @Context UriInfo uri) throws AuthorizationException { + if(AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, getClusterResourceId(), REQUIRED_AUTHORIZATIONS)) { + return handleDirectRequest(uri, MediaType.TEXT_PLAIN_TYPE); + } + else { + Response.ResponseBuilder responseBuilder = Response.status(new ResultStatus(ResultStatus.STATUS.FORBIDDEN).getStatusCode()); + responseBuilder.entity("The authenticated user is not authorized to perform this operation."); + return responseBuilder.build(); + } + } + + /** + * Get the relevant cluster's resource ID. + * + * @return a resource ID or <code>null</code> if the cluster is not found + */ + private Long getClusterResourceId() { + Long clusterResourceId = null; + + if(!StringUtils.isEmpty(clusterName)) { + try { + Cluster cluster = controllerFactory.getController().getClusters().getCluster(clusterName); + + if(cluster == null) { + LOG.warn("No cluster found with the name {}, assuming null resource id", clusterName); + } + else { + clusterResourceId = cluster.getResourceId(); + } + + } catch (AmbariException e) { + LOG.warn("An exception occurred looking up the cluster named {}, assuming null resource id: {}", + clusterName, e.getLocalizedMessage()); + } + } + else { + LOG.debug("The cluster name is not set, assuming null resource id"); + } + + return clusterResourceId; } /** http://git-wip-us.apache.org/repos/asf/ambari/blob/dc072226/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProvider.java index f496d32..d9db290 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProvider.java @@ -27,12 +27,18 @@ import org.apache.ambari.server.controller.spi.Request; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.controller.spi.SystemException; import org.apache.ambari.server.controller.utilities.PropertyHelper; +import org.apache.ambari.server.security.authorization.AuthorizationHelper; +import org.apache.ambari.server.security.authorization.ResourceType; +import org.apache.ambari.server.security.authorization.RoleAuthorization; +import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.ComponentInfo; import org.apache.ambari.server.state.LogDefinition; import org.apache.ambari.server.state.StackId; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import java.util.Collections; +import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -46,6 +52,11 @@ public class LoggingSearchPropertyProvider implements PropertyProvider { private static final String PATH_TO_SEARCH_ENGINE = "/logging/searchEngine"; + /** + * The user of authorizations for which a user must have one of in order to access LogSearch data + */ + private static final Set<RoleAuthorization> REQUIRED_AUTHORIZATIONS = EnumSet.of(RoleAuthorization.SERVICE_VIEW_OPERATIONAL_LOGS); + private static AtomicInteger errorLogCounterForLogSearchConnectionExceptions = new AtomicInteger(0); @Inject @@ -69,6 +80,16 @@ public class LoggingSearchPropertyProvider implements PropertyProvider { final String hostName = (String) resource.getPropertyValue(PropertyHelper.getPropertyId("HostRoles", "host_name")); final String clusterName = (String) resource.getPropertyValue(PropertyHelper.getPropertyId("HostRoles", "cluster_name")); + // Test to see if the authenticated user is authorized to view this data... if not, skip it. + if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, getClusterResourceID(clusterName), REQUIRED_AUTHORIZATIONS)) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("The authenticated user (%s) is not authorized to access LogSearch data for the cluster named %s", + AuthorizationHelper.getAuthenticatedName(), + clusterName)); + } + continue; + } + if (!logSearchServerRunning(clusterName)) { continue; } @@ -114,6 +135,38 @@ public class LoggingSearchPropertyProvider implements PropertyProvider { return resources; } + /** + * Get the relevant cluster's resource ID. + * + * @param clusterName a cluster name + * @return a resource ID or <code>null</code> if the cluster is not found + */ + private Long getClusterResourceID(String clusterName) { + Long clusterResourceId = null; + + if(!StringUtils.isEmpty(clusterName)) { + try { + Cluster cluster = ambariManagementController.getClusters().getCluster(clusterName); + + if(cluster == null) { + LOG.warn(String.format("No cluster found with the name %s, assuming null resource id", clusterName)); + } + else { + clusterResourceId = cluster.getResourceId(); + } + + } catch (AmbariException e) { + LOG.warn(String.format("An exception occurred looking up the cluster named %s, assuming null resource id: %s", + clusterName, e.getLocalizedMessage())); + } + } + else { + LOG.debug("The cluster name is not set, assuming null resource id"); + } + + return clusterResourceId; + } + private boolean logSearchServerRunning(String clusterName) { return loggingRequestHelperFactory.getHelper(ambariManagementController, clusterName) != null; } http://git-wip-us.apache.org/repos/asf/ambari/blob/dc072226/ambari-server/src/test/java/org/apache/ambari/server/api/services/LoggingServiceTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/LoggingServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/LoggingServiceTest.java index 28aca49..2a1f9ac 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/LoggingServiceTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/LoggingServiceTest.java @@ -21,8 +21,17 @@ import com.sun.jersey.core.util.MultivaluedMapImpl; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.logging.LoggingRequestHelperFactory; import java.net.HttpURLConnection; + +import org.apache.ambari.server.security.TestAuthenticationFactory; +import org.apache.ambari.server.security.authorization.AuthorizationHelperInitializer; +import org.apache.ambari.server.state.Cluster; +import org.apache.ambari.server.state.Clusters; import org.easymock.EasyMockSupport; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -32,8 +41,44 @@ import static org.junit.Assert.*; public class LoggingServiceTest { + @Before + @After + public void clearAuthentication() { + SecurityContextHolder.getContext().setAuthentication(null); + } + + @Test + public void testGetSearchEngineWhenLogSearchNotRunningAsAdministrator() throws Exception { + testGetSearchEngineWhenLogSearchNotRunning(TestAuthenticationFactory.createAdministrator(), true); + } + + @Test + public void testGetSearchEngineWhenLogSearchNotRunningAsClusterAdministrator() throws Exception { + testGetSearchEngineWhenLogSearchNotRunning(TestAuthenticationFactory.createClusterAdministrator(), true); + } + + @Test + public void testGetSearchEngineWhenLogSearchNotRunningAsClusterOperator() throws Exception { + testGetSearchEngineWhenLogSearchNotRunning(TestAuthenticationFactory.createClusterOperator(), true); + } + @Test - public void testGetSearchEngineWhenLogSearchNotRunning() throws Exception { + public void testGetSearchEngineWhenLogSearchNotRunningAsServiceAdministrator() throws Exception { + testGetSearchEngineWhenLogSearchNotRunning(TestAuthenticationFactory.createServiceAdministrator(), true); + } + + @Test + public void testGetSearchEngineWhenLogSearchNotRunningAsServiceOperator() throws Exception { + testGetSearchEngineWhenLogSearchNotRunning(TestAuthenticationFactory.createServiceOperator(), false); + } + + @Test + public void testGetSearchEngineWhenLogSearchNotRunningAsClusterUser() throws Exception { + testGetSearchEngineWhenLogSearchNotRunning(TestAuthenticationFactory.createClusterUser(), false); + } + + + private void testGetSearchEngineWhenLogSearchNotRunning(Authentication authentication, boolean shouldBeAuthorized) throws Exception { final String expectedClusterName = "clusterone"; final String expectedErrorMessage = "LogSearch is not currently available. If LogSearch is deployed in this cluster, please verify that the LogSearch services are running."; @@ -47,21 +92,36 @@ public class LoggingServiceTest { AmbariManagementController controllerMock = mockSupport.createMock(AmbariManagementController.class); + Clusters clustersMock = + mockSupport.createMock(Clusters.class); + + Cluster clusterMock = + mockSupport.createMock(Cluster.class); + LoggingRequestHelperFactory helperFactoryMock = mockSupport.createMock(LoggingRequestHelperFactory.class); UriInfo uriInfoMock = mockSupport.createMock(UriInfo.class); - expect(uriInfoMock.getQueryParameters()).andReturn(new MultivaluedMapImpl()).atLeastOnce(); - expect(controllerFactoryMock.getController()).andReturn(controllerMock).atLeastOnce(); + if(shouldBeAuthorized) { + expect(uriInfoMock.getQueryParameters()).andReturn(new MultivaluedMapImpl()).atLeastOnce(); - // return null from this factory, to simulate the case where LogSearch is - // not running, or is not deployed in the current cluster - expect(helperFactoryMock.getHelper(controllerMock, expectedClusterName)).andReturn(null).atLeastOnce(); + // return null from this factory, to simulate the case where LogSearch is + // not running, or is not deployed in the current cluster + expect(helperFactoryMock.getHelper(controllerMock, expectedClusterName)).andReturn(null).atLeastOnce(); + } + + expect(controllerFactoryMock.getController()).andReturn(controllerMock).atLeastOnce(); + expect(controllerMock.getClusters()).andReturn(clustersMock).once(); + expect(clustersMock.getCluster(expectedClusterName)).andReturn(clusterMock).once(); + expect(clusterMock.getResourceId()).andReturn(4L).once(); mockSupport.replayAll(); + AuthorizationHelperInitializer.viewInstanceDAOReturningNull(); + SecurityContextHolder.getContext().setAuthentication(authentication); + LoggingService loggingService = new LoggingService(expectedClusterName, controllerFactoryMock, helperFactoryMock); @@ -70,12 +130,18 @@ public class LoggingServiceTest { assertNotNull("The response returned by the LoggingService should not have been null", resource); - assertEquals("An OK status should have been returned", - HttpURLConnection.HTTP_NOT_FOUND, resource.getStatus()); - assertNotNull("A non-null Entity should have been returned", - resource.getEntity()); - assertEquals("Expected error message was not included in the response", - expectedErrorMessage, resource.getEntity()); + if(shouldBeAuthorized) { + assertEquals("An OK status should have been returned", + HttpURLConnection.HTTP_NOT_FOUND, resource.getStatus()); + assertNotNull("A non-null Entity should have been returned", + resource.getEntity()); + assertEquals("Expected error message was not included in the response", + expectedErrorMessage, resource.getEntity()); + } + else { + assertEquals("A FORBIDDEN status should have been returned", + HttpURLConnection.HTTP_FORBIDDEN, resource.getStatus()); + } mockSupport.verifyAll(); } http://git-wip-us.apache.org/repos/asf/ambari/blob/dc072226/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProviderTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProviderTest.java index af0c7df..09d15e1 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProviderTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LoggingSearchPropertyProviderTest.java @@ -1,4 +1,4 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -23,6 +23,8 @@ import org.apache.ambari.server.controller.spi.Predicate; import org.apache.ambari.server.controller.spi.Request; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.controller.utilities.PropertyHelper; +import org.apache.ambari.server.security.TestAuthenticationFactory; +import org.apache.ambari.server.security.authorization.AuthorizationHelperInitializer; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.ComponentInfo; @@ -30,7 +32,11 @@ import org.apache.ambari.server.state.LogDefinition; import org.apache.ambari.server.state.StackId; import org.easymock.Capture; import org.easymock.EasyMockSupport; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import java.util.Collections; import java.util.LinkedList; @@ -43,6 +49,7 @@ import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -59,6 +66,42 @@ import static org.junit.Assert.assertSame; */ public class LoggingSearchPropertyProviderTest { + @Before + @After + public void clearAuthentication() { + SecurityContextHolder.getContext().setAuthentication(null); + } + + @Test + public void testBasicCallAsAdministrator() throws Exception { + testBasicCall(TestAuthenticationFactory.createAdministrator(), true); + } + + @Test + public void testBasicCallAsClusterAdministrator() throws Exception { + testBasicCall(TestAuthenticationFactory.createClusterAdministrator(), true); + } + + @Test + public void testBasicCallAsClusterOperator() throws Exception { + testBasicCall(TestAuthenticationFactory.createClusterOperator(), true); + } + + @Test + public void testBasicCallAsServiceAdministrator() throws Exception { + testBasicCall(TestAuthenticationFactory.createServiceAdministrator(), true); + } + + @Test + public void testBasicCallAsServiceOperator() throws Exception { + testBasicCall(TestAuthenticationFactory.createServiceOperator(), false); + } + + @Test + public void testBasicCallAsClusterUser() throws Exception { + testBasicCall(TestAuthenticationFactory.createClusterUser(), false); + } + /** * Verifies the following: * @@ -71,10 +114,11 @@ public class LoggingSearchPropertyProviderTest { * 3. That the output of the LogSearch query is properly set on the * HostComponent resource in the expected structure. * + * @param authentication the authenticated user + * @param authorizedForLogSearch true of the user is expected to be authorized; otherwise false * @throws Exception */ - @Test - public void testBasicCall() throws Exception { + private void testBasicCall(Authentication authentication, boolean authorizedForLogSearch) throws Exception { final String expectedLogFilePath = "/var/log/hdfs/hdfs_namenode.log"; @@ -97,8 +141,11 @@ public class LoggingSearchPropertyProviderTest { expect(resourceMock.getPropertyValue(PropertyHelper.getPropertyId("HostRoles", "cluster_name"))).andReturn("clusterone").atLeastOnce(); Capture<HostComponentLoggingInfo> captureLogInfo = Capture.newInstance(); - // expect set method to be called - resourceMock.setProperty(eq("logging"), capture(captureLogInfo)); + + if(authorizedForLogSearch) { + // expect set method to be called + resourceMock.setProperty(eq("logging"), capture(captureLogInfo)); + } LogLevelQueryResponse levelQueryResponse = new LogLevelQueryResponse(); @@ -122,56 +169,62 @@ public class LoggingSearchPropertyProviderTest { AmbariManagementController controllerMock = mockSupport.createMock(AmbariManagementController.class); - AmbariMetaInfo metaInfoMock = - mockSupport.createMock(AmbariMetaInfo.class); - Clusters clustersMock = mockSupport.createMock(Clusters.class); Cluster clusterMock = mockSupport.createMock(Cluster.class); - StackId stackIdMock = - mockSupport.createMock(StackId.class); + LoggingRequestHelperFactory loggingRequestHelperFactoryMock = + mockSupport.createMock(LoggingRequestHelperFactory.class); - ComponentInfo componentInfoMock = - mockSupport.createMock(ComponentInfo.class); + LogSearchDataRetrievalService dataRetrievalServiceMock = + mockSupport.createMock(LogSearchDataRetrievalService.class); - LogDefinition logDefinitionMock = - mockSupport.createMock(LogDefinition.class); + if(authorizedForLogSearch) { + LoggingRequestHelper loggingRequestHelperMock = + mockSupport.createMock(LoggingRequestHelper.class); - LogSearchDataRetrievalService dataRetrievalServiceMock = - mockSupport.createMock(LogSearchDataRetrievalService.class); + AmbariMetaInfo metaInfoMock = + mockSupport.createMock(AmbariMetaInfo.class); - LoggingRequestHelperFactory loggingRequestHelperFactoryMock = - mockSupport.createMock(LoggingRequestHelperFactory.class); - - LoggingRequestHelper loggingRequestHelperMock = - mockSupport.createMock(LoggingRequestHelper.class); - - expect(dataRetrievalServiceMock.getLogFileNames(expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn(Collections.singleton(expectedLogFilePath)).atLeastOnce(); - expect(dataRetrievalServiceMock.getLogFileTailURI(expectedAmbariURL + expectedSearchEnginePath, expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn("").atLeastOnce(); + StackId stackIdMock = + mockSupport.createMock(StackId.class); + ComponentInfo componentInfoMock = + mockSupport.createMock(ComponentInfo.class); - expect(controllerMock.getAmbariServerURI(expectedSearchEnginePath)). - andReturn(expectedAmbariURL + expectedSearchEnginePath).atLeastOnce(); - expect(controllerMock.getAmbariMetaInfo()).andReturn(metaInfoMock).atLeastOnce(); - expect(controllerMock.getClusters()).andReturn(clustersMock).atLeastOnce(); - expect(clustersMock.getCluster("clusterone")).andReturn(clusterMock).atLeastOnce(); - expect(stackIdMock.getStackName()).andReturn(expectedStackName).atLeastOnce(); - expect(stackIdMock.getStackVersion()).andReturn(expectedStackVersion).atLeastOnce(); - expect(clusterMock.getCurrentStackVersion()).andReturn(stackIdMock).atLeastOnce(); - expect(loggingRequestHelperFactoryMock.getHelper(anyObject(AmbariManagementController.class), anyObject(String.class))) - .andReturn(loggingRequestHelperMock).atLeastOnce(); + LogDefinition logDefinitionMock = + mockSupport.createMock(LogDefinition.class); + + expect(controllerMock.getAmbariServerURI(expectedSearchEnginePath)). + andReturn(expectedAmbariURL + expectedSearchEnginePath).atLeastOnce(); + expect(controllerMock.getAmbariMetaInfo()).andReturn(metaInfoMock).atLeastOnce(); + expect(metaInfoMock.getComponentToService(expectedStackName, expectedStackVersion, expectedComponentName)).andReturn(expectedServiceName).atLeastOnce(); + expect(metaInfoMock.getComponent(expectedStackName, expectedStackVersion, expectedServiceName, expectedComponentName)).andReturn(componentInfoMock).atLeastOnce(); + expect(stackIdMock.getStackName()).andReturn(expectedStackName).atLeastOnce(); + expect(stackIdMock.getStackVersion()).andReturn(expectedStackVersion).atLeastOnce(); + expect(clusterMock.getCurrentStackVersion()).andReturn(stackIdMock).atLeastOnce(); + + expect(componentInfoMock.getLogs()).andReturn(Collections.singletonList(logDefinitionMock)).atLeastOnce(); + expect(logDefinitionMock.getLogId()).andReturn(expectedLogSearchComponentName).atLeastOnce(); - expect(metaInfoMock.getComponentToService(expectedStackName, expectedStackVersion, expectedComponentName)).andReturn(expectedServiceName).atLeastOnce(); - expect(metaInfoMock.getComponent(expectedStackName, expectedStackVersion, expectedServiceName, expectedComponentName)).andReturn(componentInfoMock).atLeastOnce(); + expect(dataRetrievalServiceMock.getLogFileNames(expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn(Collections.singleton(expectedLogFilePath)).atLeastOnce(); + expect(dataRetrievalServiceMock.getLogFileTailURI(expectedAmbariURL + expectedSearchEnginePath, expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn("").atLeastOnce(); - expect(componentInfoMock.getLogs()).andReturn(Collections.singletonList(logDefinitionMock)).atLeastOnce(); - expect(logDefinitionMock.getLogId()).andReturn(expectedLogSearchComponentName).atLeastOnce(); + expect(loggingRequestHelperFactoryMock.getHelper(anyObject(AmbariManagementController.class), anyObject(String.class))) + .andReturn(loggingRequestHelperMock).atLeastOnce(); + } + + expect(controllerMock.getClusters()).andReturn(clustersMock).atLeastOnce(); + expect(clustersMock.getCluster("clusterone")).andReturn(clusterMock).atLeastOnce(); + expect(clusterMock.getResourceId()).andReturn(4L).atLeastOnce(); mockSupport.replayAll(); + AuthorizationHelperInitializer.viewInstanceDAOReturningNull(); + SecurityContextHolder.getContext().setAuthentication(authentication); + LoggingSearchPropertyProvider propertyProvider = new LoggingSearchPropertyProvider(); @@ -188,35 +241,70 @@ public class LoggingSearchPropertyProviderTest { assertEquals("Returned resource set was of an incorrect size", 1, returnedResources.size()); - HostComponentLoggingInfo returnedLogInfo = - captureLogInfo.getValue(); + if(authorizedForLogSearch) { + HostComponentLoggingInfo returnedLogInfo = + captureLogInfo.getValue(); - assertNotNull("Returned log info should not be null", - returnedLogInfo); + assertNotNull("Returned log info should not be null", + returnedLogInfo); - assertEquals("Returned component was not the correct name", - "hdfs_namenode", returnedLogInfo.getComponentName()); + assertEquals("Returned component was not the correct name", + "hdfs_namenode", returnedLogInfo.getComponentName()); - assertEquals("Returned list of log file names for this component was incorrect", - 1, returnedLogInfo.getListOfLogFileDefinitions().size()); + assertEquals("Returned list of log file names for this component was incorrect", + 1, returnedLogInfo.getListOfLogFileDefinitions().size()); - LogFileDefinitionInfo definitionInfo = - returnedLogInfo.getListOfLogFileDefinitions().get(0); + LogFileDefinitionInfo definitionInfo = + returnedLogInfo.getListOfLogFileDefinitions().get(0); - assertEquals("Incorrect log file type was found", - LogFileType.SERVICE, definitionInfo.getLogFileType()); - assertEquals("Incorrect log file path found", - expectedLogFilePath, definitionInfo.getLogFileName()); - assertEquals("Incorrect URL path to searchEngine", - expectedAmbariURL + expectedSearchEnginePath, definitionInfo.getSearchEngineURL()); + assertEquals("Incorrect log file type was found", + LogFileType.SERVICE, definitionInfo.getLogFileType()); + assertEquals("Incorrect log file path found", + expectedLogFilePath, definitionInfo.getLogFileName()); + assertEquals("Incorrect URL path to searchEngine", + expectedAmbariURL + expectedSearchEnginePath, definitionInfo.getSearchEngineURL()); - // verify that the log level count information - // was not added to the HostComponent resource - assertNull(returnedLogInfo.getListOfLogLevels()); + // verify that the log level count information + // was not added to the HostComponent resource + assertNull(returnedLogInfo.getListOfLogLevels()); + } + else { + assertFalse("Unauthorized user should not be able to retrieve log info", captureLogInfo.hasCaptured()); + } mockSupport.verifyAll(); } + @Test + public void testBasicCallWithNullTailLogURIReturnedAsAdministrator() throws Exception { + testBasicCallWithNullTailLogURIReturned(TestAuthenticationFactory.createAdministrator(), true); + } + + @Test + public void testBasicCallWithNullTailLogURIReturnedAsClusterAdministrator() throws Exception { + testBasicCallWithNullTailLogURIReturned(TestAuthenticationFactory.createClusterAdministrator(), true); + } + + @Test + public void testBasicCallWithNullTailLogURIReturnedAsClusterOperator() throws Exception { + testBasicCallWithNullTailLogURIReturned(TestAuthenticationFactory.createClusterOperator(), true); + } + + @Test + public void testBasicCallWithNullTailLogURIReturnedAsServiceAdministrator() throws Exception { + testBasicCallWithNullTailLogURIReturned(TestAuthenticationFactory.createServiceAdministrator(), true); + } + + @Test + public void testBasicCallWithNullTailLogURIReturnedAsServiceOperator() throws Exception { + testBasicCallWithNullTailLogURIReturned(TestAuthenticationFactory.createServiceOperator(), false); + } + + @Test + public void testBasicCallWithNullTailLogURIReturnedAsClusterUser() throws Exception { + testBasicCallWithNullTailLogURIReturned(TestAuthenticationFactory.createClusterUser(), false); + } + /** * Verifies the following: * @@ -231,11 +319,11 @@ public class LoggingSearchPropertyProviderTest { * 4. That the proper error-handling is in place in the event that a null * tail log URI is returned by the retrieval service. * - * + * @param authentication the authenticated user + * @param authorizedForLogSearch true of the user is expected to be authorized; otherwise false * @throws Exception */ - @Test - public void testBasicCallWithNullTailLogURIReturned() throws Exception { + private void testBasicCallWithNullTailLogURIReturned(Authentication authentication, boolean authorizedForLogSearch) throws Exception { final String expectedLogFilePath = "/var/log/hdfs/hdfs_namenode.log"; @@ -258,8 +346,11 @@ public class LoggingSearchPropertyProviderTest { expect(resourceMock.getPropertyValue(PropertyHelper.getPropertyId("HostRoles", "cluster_name"))).andReturn("clusterone").atLeastOnce(); Capture<HostComponentLoggingInfo> captureLogInfo = Capture.newInstance(); - // expect set method to be called - resourceMock.setProperty(eq("logging"), capture(captureLogInfo)); + + if(authorizedForLogSearch) { + // expect set method to be called + resourceMock.setProperty(eq("logging"), capture(captureLogInfo)); + } LogLevelQueryResponse levelQueryResponse = new LogLevelQueryResponse(); @@ -283,21 +374,12 @@ public class LoggingSearchPropertyProviderTest { AmbariManagementController controllerMock = mockSupport.createMock(AmbariManagementController.class); - AmbariMetaInfo metaInfoMock = - mockSupport.createMock(AmbariMetaInfo.class); - Clusters clustersMock = mockSupport.createMock(Clusters.class); Cluster clusterMock = mockSupport.createMock(Cluster.class); - StackId stackIdMock = - mockSupport.createMock(StackId.class); - - ComponentInfo componentInfoMock = - mockSupport.createMock(ComponentInfo.class); - LogDefinition logDefinitionMock = mockSupport.createMock(LogDefinition.class); @@ -307,34 +389,51 @@ public class LoggingSearchPropertyProviderTest { LoggingRequestHelperFactory loggingRequestHelperFactoryMock = mockSupport.createMock(LoggingRequestHelperFactory.class); - LoggingRequestHelper loggingRequestHelperMock = - mockSupport.createMock(LoggingRequestHelper.class); + if(authorizedForLogSearch) { + AmbariMetaInfo metaInfoMock = + mockSupport.createMock(AmbariMetaInfo.class); - expect(dataRetrievalServiceMock.getLogFileNames(expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn(Collections.singleton(expectedLogFilePath)).atLeastOnce(); - // return null, to simulate the case when the LogSearch service goes down, and the helper object - // is not available to continue servicing the request. - expect(dataRetrievalServiceMock.getLogFileTailURI(expectedAmbariURL + expectedSearchEnginePath, expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn(null).atLeastOnce(); + StackId stackIdMock = + mockSupport.createMock(StackId.class); + ComponentInfo componentInfoMock = + mockSupport.createMock(ComponentInfo.class); - expect(controllerMock.getAmbariServerURI(expectedSearchEnginePath)). - andReturn(expectedAmbariURL + expectedSearchEnginePath).atLeastOnce(); - expect(controllerMock.getAmbariMetaInfo()).andReturn(metaInfoMock).atLeastOnce(); - expect(controllerMock.getClusters()).andReturn(clustersMock).atLeastOnce(); - expect(clustersMock.getCluster("clusterone")).andReturn(clusterMock).atLeastOnce(); - expect(stackIdMock.getStackName()).andReturn(expectedStackName).atLeastOnce(); - expect(stackIdMock.getStackVersion()).andReturn(expectedStackVersion).atLeastOnce(); - expect(clusterMock.getCurrentStackVersion()).andReturn(stackIdMock).atLeastOnce(); - expect(loggingRequestHelperFactoryMock.getHelper(anyObject(AmbariManagementController.class), anyObject(String.class))) - .andReturn(loggingRequestHelperMock).atLeastOnce(); + LoggingRequestHelper loggingRequestHelperMock = + mockSupport.createMock(LoggingRequestHelper.class); - expect(metaInfoMock.getComponentToService(expectedStackName, expectedStackVersion, expectedComponentName)).andReturn(expectedServiceName).atLeastOnce(); - expect(metaInfoMock.getComponent(expectedStackName, expectedStackVersion, expectedServiceName, expectedComponentName)).andReturn(componentInfoMock).atLeastOnce(); + expect(dataRetrievalServiceMock.getLogFileNames(expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn(Collections.singleton(expectedLogFilePath)).atLeastOnce(); + // return null, to simulate the case when the LogSearch service goes down, and the helper object + // is not available to continue servicing the request. + expect(dataRetrievalServiceMock.getLogFileTailURI(expectedAmbariURL + expectedSearchEnginePath, expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn(null).atLeastOnce(); - expect(componentInfoMock.getLogs()).andReturn(Collections.singletonList(logDefinitionMock)).atLeastOnce(); - expect(logDefinitionMock.getLogId()).andReturn(expectedLogSearchComponentName).atLeastOnce(); + expect(loggingRequestHelperFactoryMock.getHelper(anyObject(AmbariManagementController.class), anyObject(String.class))) + .andReturn(loggingRequestHelperMock).atLeastOnce(); + + expect(controllerMock.getAmbariServerURI(expectedSearchEnginePath)). + andReturn(expectedAmbariURL + expectedSearchEnginePath).atLeastOnce(); + expect(controllerMock.getAmbariMetaInfo()).andReturn(metaInfoMock).atLeastOnce(); + + expect(metaInfoMock.getComponentToService(expectedStackName, expectedStackVersion, expectedComponentName)).andReturn(expectedServiceName).atLeastOnce(); + expect(metaInfoMock.getComponent(expectedStackName, expectedStackVersion, expectedServiceName, expectedComponentName)).andReturn(componentInfoMock).atLeastOnce(); + + expect(componentInfoMock.getLogs()).andReturn(Collections.singletonList(logDefinitionMock)).atLeastOnce(); + expect(logDefinitionMock.getLogId()).andReturn(expectedLogSearchComponentName).atLeastOnce(); + + expect(stackIdMock.getStackName()).andReturn(expectedStackName).atLeastOnce(); + expect(stackIdMock.getStackVersion()).andReturn(expectedStackVersion).atLeastOnce(); + expect(clusterMock.getCurrentStackVersion()).andReturn(stackIdMock).atLeastOnce(); + } + + expect(controllerMock.getClusters()).andReturn(clustersMock).atLeastOnce(); + expect(clustersMock.getCluster("clusterone")).andReturn(clusterMock).atLeastOnce(); + expect(clusterMock.getResourceId()).andReturn(4L).atLeastOnce(); mockSupport.replayAll(); + AuthorizationHelperInitializer.viewInstanceDAOReturningNull(); + SecurityContextHolder.getContext().setAuthentication(authentication); + LoggingSearchPropertyProvider propertyProvider = new LoggingSearchPropertyProvider(); @@ -351,34 +450,70 @@ public class LoggingSearchPropertyProviderTest { assertEquals("Returned resource set was of an incorrect size", 1, returnedResources.size()); - HostComponentLoggingInfo returnedLogInfo = - captureLogInfo.getValue(); + if(authorizedForLogSearch) { + HostComponentLoggingInfo returnedLogInfo = + captureLogInfo.getValue(); - assertNotNull("Returned log info should not be null", - returnedLogInfo); + assertNotNull("Returned log info should not be null", + returnedLogInfo); - assertEquals("Returned component was not the correct name", - "hdfs_namenode", returnedLogInfo.getComponentName()); + assertEquals("Returned component was not the correct name", + "hdfs_namenode", returnedLogInfo.getComponentName()); - assertEquals("Returned list of log file names for this component was incorrect", - 0, returnedLogInfo.getListOfLogFileDefinitions().size()); + assertEquals("Returned list of log file names for this component was incorrect", + 0, returnedLogInfo.getListOfLogFileDefinitions().size()); - // verify that the log level count information - // was not added to the HostComponent resource - assertNull(returnedLogInfo.getListOfLogLevels()); + // verify that the log level count information + // was not added to the HostComponent resource + assertNull(returnedLogInfo.getListOfLogLevels()); + } + else { + assertFalse("Unauthorized user should not be able to retrieve log info", captureLogInfo.hasCaptured()); + } mockSupport.verifyAll(); } + @Test + public void testCheckWhenLogSearchNotAvailableAsAdministrator() throws Exception { + testCheckWhenLogSearchNotAvailable(TestAuthenticationFactory.createAdministrator(), true); + } + + @Test + public void testCheckWhenLogSearchNotAvailableAsClusterAdministrator() throws Exception { + testCheckWhenLogSearchNotAvailable(TestAuthenticationFactory.createClusterAdministrator(), true); + } + + @Test + public void testCheckWhenLogSearchNotAvailableAsClusterOperator() throws Exception { + testCheckWhenLogSearchNotAvailable(TestAuthenticationFactory.createClusterOperator(), true); + } + + @Test + public void testCheckWhenLogSearchNotAvailableAsServiceAdministrator() throws Exception { + testCheckWhenLogSearchNotAvailable(TestAuthenticationFactory.createServiceAdministrator(), true); + } + + @Test + public void testCheckWhenLogSearchNotAvailableAsServiceOperator() throws Exception { + testCheckWhenLogSearchNotAvailable(TestAuthenticationFactory.createServiceOperator(), false); + } + + @Test + public void testCheckWhenLogSearchNotAvailableAsClusterUser() throws Exception { + testCheckWhenLogSearchNotAvailable(TestAuthenticationFactory.createClusterUser(), false); + } + /** * Verifies that this property provider implementation will * properly handle the case of LogSearch not being deployed in * the cluster or available. * + * @param authentication the authenticated user + * @param authorizedForLogSearch true of the user is expected to be authorized; otherwise false * @throws Exception */ - @Test - public void testCheckWhenLogSearchNotAvailable() throws Exception { + private void testCheckWhenLogSearchNotAvailable(Authentication authentication, boolean authorizedForLogSearch) throws Exception { final String expectedStackName = "HDP"; final String expectedStackVersion = "2.4"; @@ -403,55 +538,61 @@ public class LoggingSearchPropertyProviderTest { AmbariManagementController controllerMock = mockSupport.createMock(AmbariManagementController.class); - AmbariMetaInfo metaInfoMock = - mockSupport.createMock(AmbariMetaInfo.class); - Clusters clustersMock = mockSupport.createMock(Clusters.class); Cluster clusterMock = mockSupport.createMock(Cluster.class); - StackId stackIdMock = - mockSupport.createMock(StackId.class); - - ComponentInfo componentInfoMock = - mockSupport.createMock(ComponentInfo.class); - - LogDefinition logDefinitionMock = - mockSupport.createMock(LogDefinition.class); - LogSearchDataRetrievalService dataRetrievalServiceMock = mockSupport.createMock(LogSearchDataRetrievalService.class); LoggingRequestHelperFactory loggingRequestHelperFactoryMock = mockSupport.createMock(LoggingRequestHelperFactory.class); - LoggingRequestHelper loggingRequestHelperMock = - mockSupport.createMock(LoggingRequestHelper.class); + if(authorizedForLogSearch) { + AmbariMetaInfo metaInfoMock = + mockSupport.createMock(AmbariMetaInfo.class); - expect(controllerMock.getAmbariMetaInfo()).andReturn(metaInfoMock).atLeastOnce(); - expect(controllerMock.getClusters()).andReturn(clustersMock).atLeastOnce(); - expect(clustersMock.getCluster("clusterone")).andReturn(clusterMock).atLeastOnce(); - expect(stackIdMock.getStackName()).andReturn(expectedStackName).atLeastOnce(); - expect(stackIdMock.getStackVersion()).andReturn(expectedStackVersion).atLeastOnce(); - expect(clusterMock.getCurrentStackVersion()).andReturn(stackIdMock).atLeastOnce(); - expect(loggingRequestHelperFactoryMock.getHelper(anyObject(AmbariManagementController.class), anyObject(String.class))) - .andReturn(loggingRequestHelperMock).atLeastOnce(); + StackId stackIdMock = + mockSupport.createMock(StackId.class); + + LogDefinition logDefinitionMock = + mockSupport.createMock(LogDefinition.class); - expect(metaInfoMock.getComponentToService(expectedStackName, expectedStackVersion, expectedComponentName)).andReturn(expectedServiceName).atLeastOnce(); - expect(metaInfoMock.getComponent(expectedStackName, expectedStackVersion, expectedServiceName, expectedComponentName)).andReturn(componentInfoMock).atLeastOnce(); + ComponentInfo componentInfoMock = + mockSupport.createMock(ComponentInfo.class); + LoggingRequestHelper loggingRequestHelperMock = + mockSupport.createMock(LoggingRequestHelper.class); + expect(controllerMock.getAmbariMetaInfo()).andReturn(metaInfoMock).atLeastOnce(); + expect(stackIdMock.getStackName()).andReturn(expectedStackName).atLeastOnce(); + expect(stackIdMock.getStackVersion()).andReturn(expectedStackVersion).atLeastOnce(); + expect(clusterMock.getCurrentStackVersion()).andReturn(stackIdMock).atLeastOnce(); - // simulate the case when LogSearch is not deployed, or is not available for some reason - expect(dataRetrievalServiceMock.getLogFileNames(expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn(null).atLeastOnce(); + expect(metaInfoMock.getComponentToService(expectedStackName, expectedStackVersion, expectedComponentName)).andReturn(expectedServiceName).atLeastOnce(); + expect(metaInfoMock.getComponent(expectedStackName, expectedStackVersion, expectedServiceName, expectedComponentName)).andReturn(componentInfoMock).atLeastOnce(); - expect(componentInfoMock.getLogs()).andReturn(Collections.singletonList(logDefinitionMock)).atLeastOnce(); - expect(logDefinitionMock.getLogId()).andReturn(expectedLogSearchComponentName).atLeastOnce(); + // simulate the case when LogSearch is not deployed, or is not available for some reason + expect(dataRetrievalServiceMock.getLogFileNames(expectedLogSearchComponentName, "c6401.ambari.apache.org", "clusterone")).andReturn(null).atLeastOnce(); + + expect(componentInfoMock.getLogs()).andReturn(Collections.singletonList(logDefinitionMock)).atLeastOnce(); + expect(logDefinitionMock.getLogId()).andReturn(expectedLogSearchComponentName).atLeastOnce(); + + expect(loggingRequestHelperFactoryMock.getHelper(anyObject(AmbariManagementController.class), anyObject(String.class))) + .andReturn(loggingRequestHelperMock).atLeastOnce(); + } + + expect(controllerMock.getClusters()).andReturn(clustersMock).atLeastOnce(); + expect(clustersMock.getCluster("clusterone")).andReturn(clusterMock).atLeastOnce(); + expect(clusterMock.getResourceId()).andReturn(4L).atLeastOnce(); mockSupport.replayAll(); + AuthorizationHelperInitializer.viewInstanceDAOReturningNull(); + SecurityContextHolder.getContext().setAuthentication(authentication); + LoggingSearchPropertyProvider propertyProvider = new LoggingSearchPropertyProvider(); http://git-wip-us.apache.org/repos/asf/ambari/blob/dc072226/ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java b/ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java index 80ec449..d97cd9a 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java @@ -216,6 +216,7 @@ public class TestAuthenticationFactory { RoleAuthorization.SERVICE_VIEW_CONFIGS, RoleAuthorization.SERVICE_VIEW_METRICS, RoleAuthorization.SERVICE_VIEW_STATUS_INFO, + RoleAuthorization.SERVICE_VIEW_OPERATIONAL_LOGS, RoleAuthorization.CLUSTER_MANAGE_USER_PERSISTED_DATA))); return permissionEntity; } @@ -254,6 +255,7 @@ public class TestAuthenticationFactory { RoleAuthorization.SERVICE_TOGGLE_MAINTENANCE, RoleAuthorization.SERVICE_MANAGE_CONFIG_GROUPS, RoleAuthorization.CLUSTER_MANAGE_USER_PERSISTED_DATA, + RoleAuthorization.SERVICE_VIEW_OPERATIONAL_LOGS, RoleAuthorization.CLUSTER_MANAGE_CREDENTIALS))); return permissionEntity; } @@ -287,6 +289,7 @@ public class TestAuthenticationFactory { RoleAuthorization.SERVICE_VIEW_CONFIGS, RoleAuthorization.SERVICE_VIEW_METRICS, RoleAuthorization.SERVICE_VIEW_STATUS_INFO, + RoleAuthorization.SERVICE_VIEW_OPERATIONAL_LOGS, RoleAuthorization.CLUSTER_MANAGE_USER_PERSISTED_DATA))); return permissionEntity; }
