You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by ab...@apache.org on 2024/02/06 16:51:07 UTC

(druid) branch 29.0.0 updated: Add QueryLifecycle#authorize for grpc-query-extension (#15816) (#15837)

This is an automated email from the ASF dual-hosted git repository.

abhishek pushed a commit to branch 29.0.0
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/29.0.0 by this push:
     new 6ad88fc6e54 Add QueryLifecycle#authorize for grpc-query-extension (#15816) (#15837)
6ad88fc6e54 is described below

commit 6ad88fc6e54dcbfb9b3d86cd4fa5cdeba0ac1eed
Author: Rishabh Singh <65...@users.noreply.github.com>
AuthorDate: Tue Feb 6 22:21:01 2024 +0530

    Add QueryLifecycle#authorize for grpc-query-extension (#15816) (#15837)
    
    Proposal #13469
    
    Original PR #14024
    
    A new method is being added in QueryLifecycle class to authorise a query based on authentication result.
    This method is required since we authenticate the query by intercepting it in the grpc extension and pass down the authentication result.
---
 .../org/apache/druid/server/QueryLifecycle.java    | 31 +++++++++
 .../apache/druid/server/QueryLifecycleTest.java    | 74 ++++++++++++++++------
 2 files changed, 85 insertions(+), 20 deletions(-)

diff --git a/server/src/main/java/org/apache/druid/server/QueryLifecycle.java b/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
index 0f46e5da4d9..e0bb9875240 100644
--- a/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
+++ b/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
@@ -242,6 +242,37 @@ public class QueryLifecycle
     );
   }
 
+  /**
+   * Authorize the query using the authentication result.
+   * Will return an Access object denoting whether the query is authorized or not.
+   * This method is to be used by the grpc-query-extension.
+   *
+   * @param authenticationResult authentication result indicating identity of the requester
+   * @return authorization result of requester
+   */
+  public Access authorize(AuthenticationResult authenticationResult)
+  {
+    transition(State.INITIALIZED, State.AUTHORIZING);
+    final Iterable<ResourceAction> resourcesToAuthorize = Iterables.concat(
+        Iterables.transform(
+            baseQuery.getDataSource().getTableNames(),
+            AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR
+        ),
+        Iterables.transform(
+            authConfig.contextKeysToAuthorize(userContextKeys),
+            contextParam -> new ResourceAction(new Resource(contextParam, ResourceType.QUERY_CONTEXT), Action.WRITE)
+        )
+    );
+    return doAuthorize(
+        authenticationResult,
+        AuthorizationUtils.authorizeAllResourceActions(
+            authenticationResult,
+            resourcesToAuthorize,
+            authorizerMapper
+        )
+    );
+  }
+
   private void preAuthorized(final AuthenticationResult authenticationResult, final Access access)
   {
     // gotta transition those states, even if we are already authorized
diff --git a/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java b/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
index 578661ea768..a2691297d0b 100644
--- a/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
@@ -188,15 +188,15 @@ public class QueryLifecycleTest
     EasyMock.expect(authenticationResult.getIdentity()).andReturn(IDENTITY).anyTimes();
     EasyMock.expect(authenticationResult.getAuthorizerName()).andReturn(AUTHORIZER).anyTimes();
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource(DATASOURCE, ResourceType.DATASOURCE), Action.READ))
-            .andReturn(Access.OK);
+            .andReturn(Access.OK).times(2);
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource("foo", ResourceType.QUERY_CONTEXT), Action.WRITE))
-            .andReturn(Access.OK);
+            .andReturn(Access.OK).times(2);
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource("baz", ResourceType.QUERY_CONTEXT), Action.WRITE))
-            .andReturn(Access.OK);
+            .andReturn(Access.OK).times(2);
 
     EasyMock.expect(toolChestWarehouse.getToolChest(EasyMock.anyObject()))
             .andReturn(toolChest)
-            .once();
+            .times(2);
 
     replayAll();
 
@@ -223,6 +223,10 @@ public class QueryLifecycleTest
     );
 
     Assert.assertTrue(lifecycle.authorize(mockRequest()).isAllowed());
+
+    lifecycle = createLifecycle(authConfig);
+    lifecycle.initialize(query);
+    Assert.assertTrue(lifecycle.authorize(authenticationResult).isAllowed());
   }
 
   @Test
@@ -232,13 +236,15 @@ public class QueryLifecycleTest
     EasyMock.expect(authenticationResult.getIdentity()).andReturn(IDENTITY).anyTimes();
     EasyMock.expect(authenticationResult.getAuthorizerName()).andReturn(AUTHORIZER).anyTimes();
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource(DATASOURCE, ResourceType.DATASOURCE), Action.READ))
-            .andReturn(Access.OK);
+            .andReturn(Access.OK)
+            .times(2);
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource("foo", ResourceType.QUERY_CONTEXT), Action.WRITE))
-            .andReturn(Access.DENIED);
+            .andReturn(Access.DENIED)
+            .times(2);
 
     EasyMock.expect(toolChestWarehouse.getToolChest(EasyMock.anyObject()))
             .andReturn(toolChest)
-            .once();
+            .times(2);
 
     replayAll();
 
@@ -255,6 +261,10 @@ public class QueryLifecycleTest
     QueryLifecycle lifecycle = createLifecycle(authConfig);
     lifecycle.initialize(query);
     Assert.assertFalse(lifecycle.authorize(mockRequest()).isAllowed());
+
+    lifecycle = createLifecycle(authConfig);
+    lifecycle.initialize(query);
+    Assert.assertFalse(lifecycle.authorize(authenticationResult).isAllowed());
   }
 
   @Test
@@ -264,11 +274,12 @@ public class QueryLifecycleTest
     EasyMock.expect(authenticationResult.getIdentity()).andReturn(IDENTITY).anyTimes();
     EasyMock.expect(authenticationResult.getAuthorizerName()).andReturn(AUTHORIZER).anyTimes();
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource(DATASOURCE, ResourceType.DATASOURCE), Action.READ))
-            .andReturn(Access.OK);
+            .andReturn(Access.OK)
+            .times(2);
 
     EasyMock.expect(toolChestWarehouse.getToolChest(EasyMock.anyObject()))
             .andReturn(toolChest)
-            .once();
+            .times(2);
 
     replayAll();
 
@@ -296,6 +307,10 @@ public class QueryLifecycleTest
     );
 
     Assert.assertTrue(lifecycle.authorize(mockRequest()).isAllowed());
+
+    lifecycle = createLifecycle(authConfig);
+    lifecycle.initialize(query);
+    Assert.assertTrue(lifecycle.authorize(authenticationResult).isAllowed());
   }
 
   @Test
@@ -305,11 +320,12 @@ public class QueryLifecycleTest
     EasyMock.expect(authenticationResult.getIdentity()).andReturn(IDENTITY).anyTimes();
     EasyMock.expect(authenticationResult.getAuthorizerName()).andReturn(AUTHORIZER).anyTimes();
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource(DATASOURCE, ResourceType.DATASOURCE), Action.READ))
-            .andReturn(Access.OK);
+            .andReturn(Access.OK)
+            .times(2);
 
     EasyMock.expect(toolChestWarehouse.getToolChest(EasyMock.anyObject()))
             .andReturn(toolChest)
-            .once();
+            .times(2);
 
     replayAll();
 
@@ -338,6 +354,10 @@ public class QueryLifecycleTest
     );
 
     Assert.assertTrue(lifecycle.authorize(mockRequest()).isAllowed());
+
+    lifecycle = createLifecycle(authConfig);
+    lifecycle.initialize(query);
+    Assert.assertTrue(lifecycle.authorize(authenticationResult).isAllowed());
   }
 
   @Test
@@ -347,13 +367,15 @@ public class QueryLifecycleTest
     EasyMock.expect(authenticationResult.getIdentity()).andReturn(IDENTITY).anyTimes();
     EasyMock.expect(authenticationResult.getAuthorizerName()).andReturn(AUTHORIZER).anyTimes();
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource(DATASOURCE, ResourceType.DATASOURCE), Action.READ))
-            .andReturn(Access.OK);
+            .andReturn(Access.OK)
+            .times(2);
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource("foo", ResourceType.QUERY_CONTEXT), Action.WRITE))
-            .andReturn(Access.DENIED);
+            .andReturn(Access.DENIED)
+            .times(2);
 
     EasyMock.expect(toolChestWarehouse.getToolChest(EasyMock.anyObject()))
             .andReturn(toolChest)
-            .once();
+            .times(2);
 
     replayAll();
 
@@ -373,6 +395,10 @@ public class QueryLifecycleTest
     QueryLifecycle lifecycle = createLifecycle(authConfig);
     lifecycle.initialize(query);
     Assert.assertFalse(lifecycle.authorize(mockRequest()).isAllowed());
+
+    lifecycle = createLifecycle(authConfig);
+    lifecycle.initialize(query);
+    Assert.assertFalse(lifecycle.authorize(authenticationResult).isAllowed());
   }
 
   @Test
@@ -382,22 +408,26 @@ public class QueryLifecycleTest
     EasyMock.expect(authenticationResult.getIdentity()).andReturn(IDENTITY).anyTimes();
     EasyMock.expect(authenticationResult.getAuthorizerName()).andReturn(AUTHORIZER).anyTimes();
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource("fake", ResourceType.DATASOURCE), Action.READ))
-            .andReturn(Access.OK);
+            .andReturn(Access.OK)
+            .times(2);
     EasyMock.expect(authorizer.authorize(authenticationResult, new Resource("foo", ResourceType.QUERY_CONTEXT), Action.WRITE))
-            .andReturn(Access.OK);
-    EasyMock.expect(authorizer.authorize(authenticationResult, new Resource("baz", ResourceType.QUERY_CONTEXT), Action.WRITE)).andReturn(Access.OK);
+            .andReturn(Access.OK)
+            .times(2);
+    EasyMock.expect(authorizer.authorize(authenticationResult, new Resource("baz", ResourceType.QUERY_CONTEXT), Action.WRITE))
+            .andReturn(Access.OK)
+            .times(2);
 
     EasyMock.expect(toolChestWarehouse.getToolChest(EasyMock.anyObject()))
             .andReturn(toolChest)
-            .once();
+            .times(2);
 
     replayAll();
 
     final QueryContextTest.LegacyContextQuery query = new QueryContextTest.LegacyContextQuery(ImmutableMap.of("foo", "bar", "baz", "qux"));
 
     AuthConfig authConfig = AuthConfig.newBuilder()
-        .setAuthorizeQueryContextParams(true)
-        .build();
+                                      .setAuthorizeQueryContextParams(true)
+                                      .build();
     QueryLifecycle lifecycle = createLifecycle(authConfig);
     lifecycle.initialize(query);
 
@@ -408,6 +438,10 @@ public class QueryLifecycleTest
     Assert.assertTrue(revisedContext.containsKey("queryId"));
 
     Assert.assertTrue(lifecycle.authorize(mockRequest()).isAllowed());
+
+    lifecycle = createLifecycle(authConfig);
+    lifecycle.initialize(query);
+    Assert.assertTrue(lifecycle.authorize(mockRequest()).isAllowed());
   }
 
   private HttpServletRequest mockRequest()


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org