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

roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new d4190675a0 [#12037] improvement(authz): Improve the JCasbin checks 
(#12001)
d4190675a0 is described below

commit d4190675a01f402171ffdb7db5e3313f8da6c86b
Author: roryqi <[email protected]>
AuthorDate: Fri Jul 17 15:15:43 2026 +0800

    [#12037] improvement(authz): Improve the JCasbin checks (#12001)
    
    ### What changes were proposed in this pull request?
    
    Improve the JCasbin checks
    
    ### Why are the changes needed?
    
    Fix: #12037
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    UT.
---
 .../authorization/jcasbin/JcasbinAuthorizer.java   | 154 ++++++-----
 .../jcasbin/TestJcasbinAuthorizer.java             | 295 ++++++++++++++++++++-
 2 files changed, 385 insertions(+), 64 deletions(-)

diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
index 64774c8914..47108b3799 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
@@ -24,7 +24,6 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.security.Principal;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -470,70 +469,102 @@ public class JcasbinAuthorizer implements 
GravitinoAuthorizer {
   public boolean hasSetOwnerPermission(
       String metalake, String type, String fullName, 
AuthorizationRequestContext requestContext) {
     Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
+    MetadataObject.Type metadataType = 
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT));
     MetadataObject metalakeObject =
         MetadataObjects.of(ImmutableList.of(metalake), 
MetadataObject.Type.METALAKE);
+
     // metalake owner can set owner in metalake.
     if (isOwner(currentPrincipal, metalake, metalakeObject, requestContext)) {
       return true;
     }
-    MetadataObject.Type metadataType = 
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT));
-    MetadataObject metadataObject =
-        MetadataObjects.of(Arrays.asList(fullName.split("\\.")), metadataType);
+
+    MetadataObject metadataObject = MetadataObjects.parse(fullName, 
metadataType);
     do {
       if (isOwner(currentPrincipal, metalake, metadataObject, requestContext)) 
{
-        MetadataObject.Type tempType = metadataObject.type();
-        if (tempType == MetadataObject.Type.SCHEMA) {
-          boolean hasCatalogUseCatalog =
-              authorize(
-                  currentPrincipal,
-                  metalake,
-                  MetadataObjects.parent(metadataObject),
-                  Privilege.Name.USE_CATALOG,
-                  requestContext);
-          boolean hasMetalakeUseCatalog =
-              authorize(
-                  currentPrincipal,
-                  metalake,
-                  metalakeObject,
-                  Privilege.Name.USE_CATALOG,
-                  requestContext);
-          return hasCatalogUseCatalog || hasMetalakeUseCatalog;
-        }
-        if (tempType == MetadataObject.Type.TABLE
-            || tempType == MetadataObject.Type.VIEW
-            || tempType == MetadataObject.Type.TOPIC
-            || tempType == MetadataObject.Type.FILESET
-            || tempType == MetadataObject.Type.MODEL) {
-          boolean hasMetalakeUseSchema =
-              authorize(
-                  currentPrincipal,
-                  metalake,
-                  metalakeObject,
-                  Privilege.Name.USE_SCHEMA,
-                  requestContext);
-          MetadataObject schemaObject = MetadataObjects.parent(metadataObject);
-          boolean hasCatalogUseSchema =
-              authorize(
-                  currentPrincipal,
-                  metalake,
-                  MetadataObjects.parent(schemaObject),
-                  Privilege.Name.USE_SCHEMA,
-                  requestContext);
-          boolean hasSchemaUseSchema =
-              authorize(
-                  currentPrincipal,
-                  metalake,
-                  schemaObject,
-                  Privilege.Name.USE_SCHEMA,
-                  requestContext);
-          return hasMetalakeUseSchema || hasCatalogUseSchema || 
hasSchemaUseSchema;
-        }
-        return true;
+        return hasParentUsagePermission(
+            currentPrincipal, metalake, metadataObject, metalakeObject, 
requestContext);
       }
     } while ((metadataObject = MetadataObjects.parent(metadataObject)) != 
null);
     return false;
   }
 
+  private boolean hasAuthorizeWithoutDeny(
+      Principal principal,
+      String metalake,
+      List<MetadataObject> authorizeObjects,
+      List<MetadataObject> denyObjects,
+      Privilege.Name privilege,
+      AuthorizationRequestContext requestContext) {
+    return hasAuthorizeOnAny(principal, metalake, authorizeObjects, privilege, 
requestContext)
+        && !hasDenyOnAny(principal, metalake, denyObjects, privilege, 
requestContext);
+  }
+
+  private boolean hasAuthorizeOnAny(
+      Principal principal,
+      String metalake,
+      List<MetadataObject> metadataObjects,
+      Privilege.Name privilege,
+      AuthorizationRequestContext requestContext) {
+    for (MetadataObject metadataObject : metadataObjects) {
+      if (authorize(principal, metalake, metadataObject, privilege, 
requestContext)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean hasDenyOnAny(
+      Principal principal,
+      String metalake,
+      List<MetadataObject> metadataObjects,
+      Privilege.Name privilege,
+      AuthorizationRequestContext requestContext) {
+    for (MetadataObject metadataObject : metadataObjects) {
+      if (deny(principal, metalake, metadataObject, privilege, 
requestContext)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean hasParentUsagePermission(
+      Principal principal,
+      String metalake,
+      MetadataObject targetObject,
+      MetadataObject metalakeObject,
+      AuthorizationRequestContext requestContext) {
+    MetadataObject parentObject = MetadataObjects.parent(targetObject);
+    if (parentObject != null && parentObject.type() == 
MetadataObject.Type.CATALOG) {
+      List<MetadataObject> useCatalogObjects = ImmutableList.of(parentObject, 
metalakeObject);
+      return hasAuthorizeWithoutDeny(
+          principal,
+          metalake,
+          useCatalogObjects,
+          useCatalogObjects,
+          Privilege.Name.USE_CATALOG,
+          requestContext);
+    }
+    if (parentObject != null && parentObject.type() == 
MetadataObject.Type.SCHEMA) {
+      MetadataObject catalogObject = MetadataObjects.parent(parentObject);
+      List<MetadataObject> useSchemaObjects =
+          ImmutableList.of(metalakeObject, catalogObject, parentObject);
+      return hasAuthorizeWithoutDeny(
+              principal,
+              metalake,
+              useSchemaObjects,
+              useSchemaObjects,
+              Privilege.Name.USE_SCHEMA,
+              requestContext)
+          && !hasDenyOnAny(
+              principal,
+              metalake,
+              ImmutableList.of(catalogObject, metalakeObject),
+              Privilege.Name.USE_CATALOG,
+              requestContext);
+    }
+    return true;
+  }
+
   @Override
   public boolean hasMetadataPrivilegePermission(
       String metalake, String type, String fullName, 
AuthorizationRequestContext requestContext) {
@@ -544,19 +575,20 @@ public class JcasbinAuthorizer implements 
GravitinoAuthorizer {
     } catch (IllegalArgumentException e) {
       throw new IllegalArgumentException("Unknown metadata object type: " + 
type, e);
     }
+    MetadataObject targetObject = MetadataObjects.parse(fullName, 
metadataType);
+    MetadataObject metalakeObject =
+        MetadataObjects.of(ImmutableList.of(metalake), 
MetadataObject.Type.METALAKE);
     List<MetadataObject> chain = new ArrayList<>();
-    for (MetadataObject obj = MetadataObjects.parse(fullName, metadataType);
-        obj != null;
-        obj = MetadataObjects.parent(obj)) {
+    for (MetadataObject obj = targetObject; obj != null; obj = 
MetadataObjects.parent(obj)) {
       chain.add(obj);
     }
-    chain.add(MetadataObjects.of(ImmutableList.of(metalake), 
MetadataObject.Type.METALAKE));
+    chain.add(metalakeObject);
 
-    for (MetadataObject obj : chain) {
-      if (authorize(
-          currentPrincipal, metalake, obj, Privilege.Name.MANAGE_GRANTS, 
requestContext)) {
-        return true;
-      }
+    if (hasAuthorizeWithoutDeny(
+            currentPrincipal, metalake, chain, chain, 
Privilege.Name.MANAGE_GRANTS, requestContext)
+        && hasParentUsagePermission(
+            currentPrincipal, metalake, targetObject, metalakeObject, 
requestContext)) {
+      return true;
     }
     return hasSetOwnerPermission(metalake, type, fullName, requestContext);
   }
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
index 9932a73858..d13437d5d9 100644
--- 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
@@ -19,6 +19,7 @@ package org.apache.gravitino.server.authorization.jcasbin;
 
 import static org.apache.gravitino.authorization.Privilege.Name.SELECT_TABLE;
 import static org.apache.gravitino.authorization.Privilege.Name.USE_CATALOG;
+import static org.apache.gravitino.authorization.Privilege.Name.USE_SCHEMA;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -1685,7 +1686,13 @@ public class TestJcasbinAuthorizer {
             "metalakeGrantRole",
             ImmutableList.of(
                 buildManageGrantsSecurableObject(
-                    metalakeGrantRoleId, MetadataObject.Type.METALAKE, 
METALAKE)));
+                    metalakeGrantRoleId, MetadataObject.Type.METALAKE, 
METALAKE),
+                buildSecurableObject(
+                    metalakeGrantRoleId,
+                    MetadataObject.Type.SCHEMA,
+                    "testCatalog.testSchema",
+                    USE_SCHEMA,
+                    "ALLOW")));
     when(entityStore.get(
             eq(NameIdentifierUtil.ofRole(METALAKE, metalakeGrantRole.name())),
             eq(Entity.EntityType.ROLE),
@@ -1708,7 +1715,19 @@ public class TestJcasbinAuthorizer {
             "catalogGrantRole",
             ImmutableList.of(
                 buildManageGrantsSecurableObject(
-                    catalogGrantRoleId, MetadataObject.Type.CATALOG, 
"testCatalog")));
+                    catalogGrantRoleId, MetadataObject.Type.CATALOG, 
"testCatalog"),
+                buildSecurableObject(
+                    catalogGrantRoleId,
+                    MetadataObject.Type.CATALOG,
+                    "testCatalog",
+                    USE_CATALOG,
+                    "ALLOW"),
+                buildSecurableObject(
+                    catalogGrantRoleId,
+                    MetadataObject.Type.SCHEMA,
+                    "testCatalog.testSchema",
+                    USE_SCHEMA,
+                    "ALLOW")));
     when(entityStore.get(
             eq(NameIdentifierUtil.ofRole(METALAKE, catalogGrantRole.name())),
             eq(Entity.EntityType.ROLE),
@@ -1737,7 +1756,13 @@ public class TestJcasbinAuthorizer {
                 buildManageGrantsSecurableObject(
                     tableGrantRoleId,
                     MetadataObject.Type.TABLE,
-                    "testCatalog.testSchema.testTable")));
+                    "testCatalog.testSchema.testTable"),
+                buildSecurableObject(
+                    tableGrantRoleId,
+                    MetadataObject.Type.SCHEMA,
+                    "testCatalog.testSchema",
+                    USE_SCHEMA,
+                    "ALLOW")));
     when(entityStore.get(
             eq(NameIdentifierUtil.ofRole(METALAKE, tableGrantRole.name())),
             eq(Entity.EntityType.ROLE),
@@ -1760,6 +1785,246 @@ public class TestJcasbinAuthorizer {
                 METALAKE, "INVALID_TYPE", "testCatalog", new 
AuthorizationRequestContext()));
   }
 
+  @Test
+  public void testHasMetadataPrivilegePermissionRejectsDenyManageGrants() 
throws Exception {
+    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
+
+    Long allowRoleId = 203L;
+    RoleEntity allowRole =
+        mockRoleInStore(
+            allowRoleId,
+            "allowManageGrantsRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    allowRoleId,
+                    MetadataObject.Type.TABLE,
+                    "testCatalog.testSchema.testTable",
+                    Privilege.Name.MANAGE_GRANTS,
+                    "ALLOW"),
+                buildSecurableObject(
+                    allowRoleId,
+                    MetadataObject.Type.SCHEMA,
+                    "testCatalog.testSchema",
+                    USE_SCHEMA,
+                    "ALLOW")));
+    Long denyRoleId = 204L;
+    RoleEntity denyRole =
+        mockRoleInStore(
+            denyRoleId,
+            "denyManageGrantsRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    denyRoleId,
+                    MetadataObject.Type.METALAKE,
+                    METALAKE,
+                    Privilege.Name.MANAGE_GRANTS,
+                    "DENY")));
+    mockDirectUserRoles(allowRole, denyRole);
+
+    assertFalse(
+        jcasbinAuthorizer.hasMetadataPrivilegePermission(
+            METALAKE,
+            "TABLE",
+            "testCatalog.testSchema.testTable",
+            new AuthorizationRequestContext()),
+        "DENY MANAGE_GRANTS should override a narrower ALLOW MANAGE_GRANTS");
+  }
+
+  @Test
+  public void testHasMetadataPrivilegePermissionRejectsMissingParentUsage() 
throws Exception {
+    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
+
+    Long allowRoleId = 209L;
+    RoleEntity allowRole =
+        mockRoleInStore(
+            allowRoleId,
+            "allowManageGrantsWithoutUseRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    allowRoleId,
+                    MetadataObject.Type.TABLE,
+                    "testCatalog.testSchema.testTable",
+                    Privilege.Name.MANAGE_GRANTS,
+                    "ALLOW")));
+    mockDirectUserRoles(allowRole);
+
+    assertFalse(
+        jcasbinAuthorizer.hasMetadataPrivilegePermission(
+            METALAKE,
+            "TABLE",
+            "testCatalog.testSchema.testTable",
+            new AuthorizationRequestContext()),
+        "MANAGE_GRANTS on a table should also require parent USE_SCHEMA");
+  }
+
+  @Test
+  public void 
testHasMetadataPrivilegePermissionRejectsDenyParentUsageForFunction()
+      throws Exception {
+    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
+
+    Long allowRoleId = 210L;
+    RoleEntity allowRole =
+        mockRoleInStore(
+            allowRoleId,
+            "allowFunctionManageGrantsRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    allowRoleId,
+                    MetadataObject.Type.FUNCTION,
+                    "testCatalog.testSchema.testFunction",
+                    Privilege.Name.MANAGE_GRANTS,
+                    "ALLOW"),
+                buildSecurableObject(
+                    allowRoleId,
+                    MetadataObject.Type.SCHEMA,
+                    "testCatalog.testSchema",
+                    USE_SCHEMA,
+                    "ALLOW")));
+    Long denyRoleId = 211L;
+    RoleEntity denyRole =
+        mockRoleInStore(
+            denyRoleId,
+            "denyUseSchemaForFunctionRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    denyRoleId, MetadataObject.Type.METALAKE, METALAKE, 
USE_SCHEMA, "DENY")));
+    mockDirectUserRoles(allowRole, denyRole);
+
+    assertFalse(
+        jcasbinAuthorizer.hasMetadataPrivilegePermission(
+            METALAKE,
+            "FUNCTION",
+            "testCatalog.testSchema.testFunction",
+            new AuthorizationRequestContext()),
+        "DENY USE_SCHEMA should override FUNCTION-level MANAGE_GRANTS");
+  }
+
+  @Test
+  public void 
testHasMetadataPrivilegePermissionAllowsOwnerWithDenyManageGrants() throws 
Exception {
+    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
+
+    Long denyRoleId = 205L;
+    RoleEntity denyRole =
+        mockRoleInStore(
+            denyRoleId,
+            "denyManageGrantsForOwnerRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    denyRoleId,
+                    MetadataObject.Type.METALAKE,
+                    METALAKE,
+                    Privilege.Name.MANAGE_GRANTS,
+                    "DENY")));
+    mockDirectUserRoles(denyRole);
+    GravitinoCache<Long, Optional<OwnerInfo>> ownerRelCache = 
getOwnerRelCache(jcasbinAuthorizer);
+    ownerRelCache.invalidateAll();
+    ownerRelCache.put(CATALOG_ID, Optional.of(new OwnerInfo(USER_ID, "USER")));
+
+    assertTrue(
+        jcasbinAuthorizer.hasMetadataPrivilegePermission(
+            METALAKE, "CATALOG", "testCatalog", new 
AuthorizationRequestContext()),
+        "Owner should be able to manage privileges without checking DENY 
MANAGE_GRANTS");
+  }
+
+  @Test
+  public void testHasSetOwnerPermissionRejectsDenyUseCatalogForTableOwner() 
throws Exception {
+    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
+
+    Long allowRoleId = 206L;
+    RoleEntity allowRole =
+        mockRoleInStore(
+            allowRoleId,
+            "allowUseSchemaRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    allowRoleId,
+                    MetadataObject.Type.SCHEMA,
+                    "testCatalog.testSchema",
+                    USE_SCHEMA,
+                    "ALLOW")));
+    Long denyRoleId = 207L;
+    RoleEntity denyRole =
+        mockRoleInStore(
+            denyRoleId,
+            "denyUseCatalogRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    denyRoleId, MetadataObject.Type.METALAKE, METALAKE, 
USE_CATALOG, "DENY")));
+    mockDirectUserRoles(allowRole, denyRole);
+    when(ownerMetaMapper.selectOwnerByMetadataObjectIdAndType(eq(CATALOG_ID), 
eq("TABLE")))
+        .thenReturn(new OwnerInfo(USER_ID, "USER"));
+    getOwnerRelCache(jcasbinAuthorizer).invalidateAll();
+
+    assertFalse(
+        jcasbinAuthorizer.hasSetOwnerPermission(
+            METALAKE,
+            "TABLE",
+            "testCatalog.testSchema.testTable",
+            new AuthorizationRequestContext()),
+        "DENY USE_CATALOG should override table ownership when setting owner");
+  }
+
+  @Test
+  public void testHasSetOwnerPermissionRejectsDenyUseCatalogForFunctionOwner() 
throws Exception {
+    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
+
+    Long allowRoleId = 212L;
+    RoleEntity allowRole =
+        mockRoleInStore(
+            allowRoleId,
+            "allowUseSchemaForFunctionOwnerRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    allowRoleId,
+                    MetadataObject.Type.SCHEMA,
+                    "testCatalog.testSchema",
+                    USE_SCHEMA,
+                    "ALLOW")));
+    Long denyRoleId = 213L;
+    RoleEntity denyRole =
+        mockRoleInStore(
+            denyRoleId,
+            "denyUseCatalogForFunctionOwnerRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    denyRoleId, MetadataObject.Type.METALAKE, METALAKE, 
USE_CATALOG, "DENY")));
+    mockDirectUserRoles(allowRole, denyRole);
+    when(ownerMetaMapper.selectOwnerByMetadataObjectIdAndType(eq(CATALOG_ID), 
eq("FUNCTION")))
+        .thenReturn(new OwnerInfo(USER_ID, "USER"));
+    getOwnerRelCache(jcasbinAuthorizer).invalidateAll();
+
+    assertFalse(
+        jcasbinAuthorizer.hasSetOwnerPermission(
+            METALAKE,
+            "FUNCTION",
+            "testCatalog.testSchema.testFunction",
+            new AuthorizationRequestContext()),
+        "DENY USE_CATALOG should override function ownership when setting 
owner");
+  }
+
+  @Test
+  public void testHasSetOwnerPermissionAllowsCatalogOwnerWithDenyUseCatalog() 
throws Exception {
+    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
+
+    Long denyRoleId = 208L;
+    RoleEntity denyRole =
+        mockRoleInStore(
+            denyRoleId,
+            "denyUseCatalogForCatalogOwnerRole",
+            ImmutableList.of(
+                buildSecurableObject(
+                    denyRoleId, MetadataObject.Type.METALAKE, METALAKE, 
USE_CATALOG, "DENY")));
+    mockDirectUserRoles(denyRole);
+    GravitinoCache<Long, Optional<OwnerInfo>> ownerRelCache = 
getOwnerRelCache(jcasbinAuthorizer);
+    ownerRelCache.invalidateAll();
+    ownerRelCache.put(CATALOG_ID, Optional.of(new OwnerInfo(USER_ID, "USER")));
+
+    assertTrue(
+        jcasbinAuthorizer.hasSetOwnerPermission(
+            METALAKE, "CATALOG", "testCatalog", new 
AuthorizationRequestContext()),
+        "Catalog owner should be able to set owner without checking DENY 
USE_CATALOG");
+  }
+
   /**
    * Builds a {@link SecurableObject} carrying an ALLOW {@code MANAGE_GRANTS} 
privilege bound to
    * {@code type} with the shared test metadata ID ({@link #CATALOG_ID}).
@@ -1786,6 +2051,30 @@ public class TestJcasbinAuthorizer {
     }
   }
 
+  private static SecurableObject buildSecurableObject(
+      Long roleId,
+      MetadataObject.Type type,
+      String objectName,
+      Privilege.Name privilege,
+      String condition) {
+    try {
+      SecurableObjectPO po =
+          SecurableObjectPO.builder()
+              .withType(String.valueOf(type))
+              .withMetadataObjectId(CATALOG_ID)
+              .withRoleId(roleId)
+              
.withPrivilegeNames(objectMapper.writeValueAsString(ImmutableList.of(privilege)))
+              
.withPrivilegeConditions(objectMapper.writeValueAsString(ImmutableList.of(condition)))
+              .withDeletedAt(0L)
+              .withCurrentVersion(1L)
+              .withLastVersion(1L)
+              .build();
+      return POConverters.fromSecurableObjectPO(objectName, po, type);
+    } catch (JsonProcessingException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   @SuppressWarnings("unchecked")
   private static GravitinoCache<Long, Long> 
getLoadedRolesCache(JcasbinAuthorizer authorizer)
       throws Exception {

Reply via email to