KNOX-2770 - KnoxToken doAs support depends on token state service and a service-level configuration (#609)

diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
index 42fc815..ee8d100 100644
--- a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
+++ b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
@@ -145,7 +145,7 @@
   private static final String TARGET_ENDPOINT_PULIC_CERT_PEM = TOKEN_PARAM_PREFIX + "target.endpoint.cert.pem";
   static final String QUERY_PARAMETER_DOAS = "doAs";
   static final String PROXYUSER_PREFIX = TOKEN_PARAM_PREFIX + "proxyuser";
-  private static final String IMPERSONATION_ENABLED_PARAM = TOKEN_PARAM_PREFIX + "impersonation.enabled";
+  static final String IMPERSONATION_ENABLED_PARAM = TOKEN_PARAM_PREFIX + "impersonation.enabled";
   private static final String IMPERSONATION_ENABLED_TEXT = "impersonationEnabled";
   public static final String KNOX_TOKEN_INCLUDE_GROUPS = TOKEN_PARAM_PREFIX + "include.groups";
   public static final String KNOX_TOKEN_ISSUER = TOKEN_PARAM_PREFIX + "issuer";
@@ -173,6 +173,7 @@
   private int tokenLimitPerUser;
   private boolean includeGroupsInTokenAllowed;
   private String tokenIssuer;
+  private boolean impersonationEnabled;
 
   enum UserLimitExceededAction {REMOVE_OLDEST, RETURN_ERROR};
   private UserLimitExceededAction userLimitExceededAction = UserLimitExceededAction.RETURN_ERROR;
@@ -269,6 +270,12 @@
       endpointPublicCert = targetEndpointPublicCert;
     }
 
+    // KnoxToken impersonation should be configurable regardless of the token state
+    // management status (i.e. even if token state management is enabled users
+    // should be able to opt-out token impersonation
+    final String impersonationEnabledValue = context.getInitParameter(IMPERSONATION_ENABLED_PARAM);
+    impersonationEnabled = impersonationEnabledValue == null ? Boolean.TRUE : Boolean.parseBoolean(impersonationEnabledValue);
+
     // If server-managed token expiration is configured, set the token state service
     if (isServerManagedTokenStateEnabled()) {
       String topologyName = getTopologyName();
@@ -314,11 +321,15 @@
       } else {
         log.noRenewersConfigured(topologyName);
       }
+
+      // refreshing Hadoop ProxyUser groups config only makes sense if token state management is turned on
+      // and impersonation is enabled
+      if (impersonationEnabled) {
+        final Configuration conf = AuthFilterUtils.getProxyUserConfiguration(context, PROXYUSER_PREFIX);
+        ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
+      }
     }
     setTokenStateServiceStatusMap();
-
-    final Configuration conf = AuthFilterUtils.getProxyUserConfiguration(context, PROXYUSER_PREFIX);
-    ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
   }
 
   private String getTokenTTLAsText() {
@@ -368,9 +379,7 @@
     final Boolean lifespanInputEnabled = lifespanInputEnabledValue == null ? Boolean.TRUE : Boolean.parseBoolean(lifespanInputEnabledValue);
     tokenStateServiceStatusMap.put(LIFESPAN_INPUT_ENABLED_TEXT, lifespanInputEnabled.toString());
 
-    final String impersonationEnabledValue = context.getInitParameter(IMPERSONATION_ENABLED_PARAM);
-    final Boolean impersonationEnabled = impersonationEnabledValue == null ? Boolean.TRUE : Boolean.parseBoolean(impersonationEnabledValue);
-    tokenStateServiceStatusMap.put(IMPERSONATION_ENABLED_TEXT, impersonationEnabled.toString());
+    tokenStateServiceStatusMap.put(IMPERSONATION_ENABLED_TEXT, Boolean.toString(impersonationEnabled));
   }
 
   private void populateAllowedTokenStateBackendForTokenGenApp(final String actualTokenServiceName) {
@@ -711,7 +720,8 @@
     String userName = request.getUserPrincipal().getName();
     String createdBy = null;
     // checking the doAs user only makes sense if tokens are managed (this is where we store the userName information)
-    if (tokenStateService != null) {
+    // and if impersonation is enabled
+    if (impersonationEnabled && tokenStateService != null) {
       final String doAsUser = request.getParameter(QUERY_PARAMETER_DOAS);
       if (doAsUser != null && !doAsUser.equals(userName)) {
         try {
diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index 6812b19..bf06269 100644
--- a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++ b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@ -170,6 +170,9 @@
     if (contextExpectations.containsKey(TokenResource.QUERY_PARAMETER_DOAS)) {
       EasyMock.expect(request.getParameter(TokenResource.QUERY_PARAMETER_DOAS)).andReturn(contextExpectations.get(TokenResource.QUERY_PARAMETER_DOAS)).anyTimes();
     }
+    if (contextExpectations.containsKey(TokenResource.IMPERSONATION_ENABLED_PARAM)) {
+      EasyMock.expect(request.getParameter(TokenResource.IMPERSONATION_ENABLED_PARAM)).andReturn(contextExpectations.get(TokenResource.IMPERSONATION_ENABLED_PARAM)).anyTimes();
+    }
     EasyMock.expect(request.getParameterNames()).andReturn(Collections.emptyEnumeration()).anyTimes();
 
     GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
@@ -1102,11 +1105,21 @@
 
   @Test
   public void testCreateImpersonatedToken() throws Exception {
+    testCreateImpersonatedToken(true);
+  }
+
+  @Test
+  public void testImpersonationDisabled() throws Exception {
+    testCreateImpersonatedToken(false);
+  }
+
+  private void testCreateImpersonatedToken(boolean enableImpersonation) throws Exception {
     final String impersonatedUser = "testUser";
     final Map<String, String> contextExpectations = new HashMap<>();
     contextExpectations.put(TokenResource.QUERY_PARAMETER_DOAS, impersonatedUser);
     contextExpectations.put(TokenResource.PROXYUSER_PREFIX + "." + USER_NAME + ".users", impersonatedUser);
     contextExpectations.put(TokenResource.PROXYUSER_PREFIX + "." + USER_NAME + ".hosts", "*");
+    contextExpectations.put(TokenResource.IMPERSONATION_ENABLED_PARAM, Boolean.toString(enableImpersonation));
     configureCommonExpectations(contextExpectations, Boolean.TRUE);
 
     final TokenResource tr = new TokenResource();
@@ -1116,13 +1129,18 @@
 
     tr.doGet();
 
-    final Response getKnoxTokensResponse = getUserTokensResponse(tr, true);
+    final Response getKnoxTokensResponse = getUserTokensResponse(tr, enableImpersonation);
     final Collection<LinkedHashMap<String, Object>> tokens = ((Map<String, Collection<LinkedHashMap<String, Object>>>) JsonUtils
         .getObjectFromJsonString(getKnoxTokensResponse.getEntity().toString())).get("tokens");
     final LinkedHashMap<String, Object> knoxToken = tokens.iterator().next();
     final Map<String, String> metadata = (Map<String, String>) knoxToken.get("metadata");
-    assertEquals(metadata.get("createdBy"), USER_NAME);
-    assertEquals(metadata.get("userName"), impersonatedUser);
+    if (enableImpersonation) {
+      assertEquals(metadata.get("createdBy"), USER_NAME);
+      assertEquals(metadata.get("userName"), impersonatedUser);
+    } else {
+      assertNull(metadata.get("createdBy"));
+      assertEquals(USER_NAME, metadata.get("userName"));
+    }
   }
 
   @Test