This is an automated email from the ASF dual-hosted git repository. jshao pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit ba35f50e9e86c9a4edd870bbea22d19cc57a9d23 Author: yangyang zhong <[email protected]> AuthorDate: Wed May 21 15:25:27 2025 +0800 [#6827] feat(authz): Jcasbin model file for Gravitino (#7086) ### What changes were proposed in this pull request? Define the jcasbin permission model ### Why are the changes needed? Fix: #6827 ### Does this PR introduce _any_ user-facing change? None ### How was this patch tested? org.apache.gravitino.server.authorization.TestJcasbinModel --- .../src/main/resources/jcasbin_model.conf | 55 ++ .../server/authorization/TestJcasbinModel.java | 569 +++++++++++++++++++++ .../src/test/resources/jcasbin_policy.txt | 28 + 3 files changed, 652 insertions(+) diff --git a/server-common/src/main/resources/jcasbin_model.conf b/server-common/src/main/resources/jcasbin_model.conf new file mode 100644 index 0000000000..3247012b3b --- /dev/null +++ b/server-common/src/main/resources/jcasbin_model.conf @@ -0,0 +1,55 @@ +; 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 +; regarding copyright ownership. The ASF licenses this file +; to you under the Apache License, Version 2.0 (the +; "License"); you may not use this file except in compliance +; with the License. You may obtain a copy of the License at +; +; http://www.apache.org/licenses/LICENSE-2.0 +; +; Unless required by applicable law or agreed to in writing, +; software distributed under the License is distributed on an +; "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +; KIND, either express or implied. See the License for the +; specific language governing permissions and limitations +; under the License. + +; The model file of jcasbin defines a permission model used to describe the permissions +; of users, groups, and roles for a single metadata type. + +; "r" represents the parameters passed when making a request to jCasbin. +; "sub" represents a roleId, userId, or groupCodeId. +; The combination of `sub` and `metalakeId` uniquely identifies a user, role, or group. +; "metadataType" represents the type of metadata. +; "metadataId" represents the id of metadata +; "act" represents the privilege that needs to be authorized or whether it is an OWNER. +[request_definition] +r = sub, metadataType, metadataId, act + +; "p" represents a permission policy. +; "eft" stands for "effect" which can be either "allow" or "deny". +[policy_definition] +p = sub, metadataType, metadataId, act, eft + +; "g" represents the membership or ownership relationship of users, groups, or roles. +[role_definition] +g = _, _ + +; "e" represents the effect of the "eft",eft performs a logical combination judgment on the matching results +; of Matchers. +; e = some(where(p.eft == allow)): This statement means that if the matching strategy result p.eft has the result +; of (some) allow +; e = some(where (p.eft == allow)) && !some(where (p.eft == deny)): The logical meaning of this example combination +; is: if there is a strategy that matches the result of allow and no strategy that matches the result of deny, +; the result is true. In other words, it is true when the matching strategies are all allow. If there is any deny, +; both are false (more simply, when allow and deny exist at the same time, deny takes precedence). +; +; see more in https://casbin.org/zh/docs/how-it-works/#effect +[policy_effect] +e = some(where (p.eft == allow)) && !some(where (p.eft == deny)) + +; "m" represents the matching rules of the model +[matchers] +m = g(r.sub, p.sub) && r.metadataId == p.metadataId && r.metadataType == p.metadataType && ( p.act == "OWNER" || r.act == p.act ) + diff --git a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestJcasbinModel.java b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestJcasbinModel.java new file mode 100644 index 0000000000..43410b8f28 --- /dev/null +++ b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestJcasbinModel.java @@ -0,0 +1,569 @@ +/* + * 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 + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.gravitino.server.authorization; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.gravitino.MetadataObject; +import org.apache.gravitino.authorization.Privilege; +import org.casbin.jcasbin.main.Enforcer; +import org.casbin.jcasbin.model.Model; +import org.casbin.jcasbin.persist.file_adapter.FileAdapter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** Test for jcasbin model. */ +public class TestJcasbinModel { + + /** Jcasbin enforcer */ + private static Enforcer enforcer; + + @BeforeAll + public static void init() throws IOException { + // Load jcasbin policy from jcasbin_policy.txt + URL resource = TestJcasbinModel.class.getResource("/jcasbin_policy.txt"); + Assertions.assertNotNull(resource); + List<String> policyWithLicense = + FileUtils.readLines(new File(resource.getFile()), StandardCharsets.UTF_8); + // remove license + String policy = + policyWithLicense.stream() + .filter(line -> !line.startsWith("#")) + .collect(Collectors.joining("\n")); + try (InputStream modelStream = + TestJcasbinModel.class.getResourceAsStream("/jcasbin_model.conf"); + InputStream policyInputStream = + new ByteArrayInputStream(policy.getBytes(StandardCharsets.UTF_8))) { + Assertions.assertNotNull(modelStream); + String modelString = IOUtils.toString(modelStream, StandardCharsets.UTF_8); + Model model = new Model(); + model.loadModelFromText(modelString); + FileAdapter fileAdapter = new FileAdapter(policyInputStream); + enforcer = new Enforcer(model, fileAdapter); + } + } + + /** + * "role1" is the OWNER of metalake1. Determine whether role1 has the OWNER permission for + * metalake1. + */ + @Test + public void testMetalakeOwner() { + Assertions.assertTrue( + enforcer.enforce("role1", MetadataObject.Type.METALAKE.name(), "metalake1", "OWNER")); + Assertions.assertTrue( + enforcer.enforce( + "role1", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce("role1", MetadataObject.Type.METALAKE.name(), "metalake2", "OWNER")); + Assertions.assertFalse( + enforcer.enforce( + "role1", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.USE_CATALOG.name())); + } + + /** + * "role2" is the OWNER of catalog1. Determine whether role2 has the OWNER permission for + * catalog2. + */ + @Test + public void testCatalogOwner() { + Assertions.assertTrue( + enforcer.enforce("role2", MetadataObject.Type.CATALOG.name(), "catalog1", "OWNER")); + Assertions.assertTrue( + enforcer.enforce( + "role2", + MetadataObject.Type.CATALOG.name(), + "catalog1", + Privilege.Name.USE_SCHEMA.name())); + Assertions.assertTrue( + enforcer.enforce( + "role2", + MetadataObject.Type.CATALOG.name(), + "catalog1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce("role2", MetadataObject.Type.CATALOG.name(), "catalog2", "OWNER")); + Assertions.assertFalse( + enforcer.enforce( + "role2", + MetadataObject.Type.CATALOG.name(), + "catalog2", + Privilege.Name.USE_CATALOG.name())); + } + + /** + * role3 is the owner of schema1. Determine whether role3 has the owner permission for schema1. + */ + @Test + public void testSchemaOwner() { + Assertions.assertTrue( + enforcer.enforce("role3", MetadataObject.Type.SCHEMA.name(), "schema1", "OWNER")); + Assertions.assertTrue( + enforcer.enforce( + "role3", + MetadataObject.Type.SCHEMA.name(), + "schema1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce("role3", MetadataObject.Type.SCHEMA.name(), "schema2", "OWNER")); + Assertions.assertFalse( + enforcer.enforce( + "role3", + MetadataObject.Type.SCHEMA.name(), + "schema2", + Privilege.Name.SELECT_TABLE.name())); + } + + /** "role4" is the owner of table1. Determine whether role4 has the permissions for table1. */ + @Test + public void testTableOwner() { + Assertions.assertTrue( + enforcer.enforce("role4", MetadataObject.Type.TABLE.name(), "table1", "OWNER")); + Assertions.assertTrue( + enforcer.enforce( + "role4", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + Assertions.assertTrue( + enforcer.enforce( + "role4", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce("role3", MetadataObject.Type.SCHEMA.name(), "table1", "OWNER")); + Assertions.assertFalse( + enforcer.enforce("role3", MetadataObject.Type.SCHEMA.name(), "table2", "OWNER")); + Assertions.assertFalse( + enforcer.enforce( + "role4", + MetadataObject.Type.TABLE.name(), + "table2", + Privilege.Name.SELECT_TABLE.name())); + } + + /** Check whether a role can have privileges. */ + @Test + public void testRolePrivilege() { + // "role5" has partial privilege. + Assertions.assertFalse( + enforcer.enforce("role5", MetadataObject.Type.METALAKE.name(), "metalake1", "OWNER")); + + Assertions.assertTrue( + enforcer.enforce( + "role5", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertTrue( + enforcer.enforce( + "role5", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertTrue( + enforcer.enforce( + "role5", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "role5", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "role5", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "role5", + MetadataObject.Type.TABLE.name(), + "table2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "role5", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + + // role1000 has no privilege. + Assertions.assertFalse( + enforcer.enforce("role1000", MetadataObject.Type.METALAKE.name(), "metalake1", "OWNER")); + + Assertions.assertFalse( + enforcer.enforce( + "role1000", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "role1000", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "role1000", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "role1000", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "role1000", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "role1000", + MetadataObject.Type.TABLE.name(), + "table2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "role1000", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + } + + /** + * Determine whether a group, after being assigned a role, can inherit the permissions of that + * role. + */ + @Test + public void testGroupPrivilege() { + // "group1" possesses role5, and therefore, group5 has the permissions of role5. + Assertions.assertFalse( + enforcer.enforce("group1", MetadataObject.Type.METALAKE.name(), "metalake1", "OWNER")); + + Assertions.assertTrue( + enforcer.enforce( + "group1", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertTrue( + enforcer.enforce( + "group1", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertTrue( + enforcer.enforce( + "group1", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1", + MetadataObject.Type.TABLE.name(), + "table2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + + // group1000 has no roles and therefore has no permissions. + Assertions.assertFalse( + enforcer.enforce("group1000", MetadataObject.Type.METALAKE.name(), "metalake1", "OWNER")); + + Assertions.assertFalse( + enforcer.enforce( + "group1000", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1000", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1000", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1000", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1000", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1000", + MetadataObject.Type.TABLE.name(), + "table2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "group1000", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + } + + /** + * Determine whether a user can have the corresponding permissions after being granted a role or + * belonging to a user group. + */ + @Test + public void testUserPrivilege() { + // ”user1" has the privilege of role5. + Assertions.assertFalse( + enforcer.enforce("user1", MetadataObject.Type.METALAKE.name(), "metalake1", "OWNER")); + + Assertions.assertTrue( + enforcer.enforce( + "user1", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertTrue( + enforcer.enforce( + "user1", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertTrue( + enforcer.enforce( + "user1", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1", + MetadataObject.Type.TABLE.name(), + "table2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + + // ”user2" has the privilege of group1. + Assertions.assertFalse( + enforcer.enforce("user2", MetadataObject.Type.METALAKE.name(), "metalake1", "OWNER")); + + Assertions.assertTrue( + enforcer.enforce( + "user2", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertTrue( + enforcer.enforce( + "user2", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "user2", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user2", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user2", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "user2", + MetadataObject.Type.TABLE.name(), + "table2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user2", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + + // "user3" has the privilege of both role1 and group1. + Assertions.assertTrue( + enforcer.enforce("user3", MetadataObject.Type.METALAKE.name(), "metalake1", "OWNER")); + + Assertions.assertTrue( + enforcer.enforce( + "user3", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertTrue( + enforcer.enforce( + "user3", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "user3", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user3", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user3", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "user3", + MetadataObject.Type.TABLE.name(), + "table2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user3", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + + Assertions.assertFalse( + enforcer.enforce("user1000", MetadataObject.Type.METALAKE.name(), "metalake1", "OWNER")); + + // "user1000" has no roles and therefore has no permissions. + Assertions.assertFalse( + enforcer.enforce( + "user1000", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1000", + MetadataObject.Type.METALAKE.name(), + "metalake1", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1000", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.MODIFY_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1000", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.SELECT_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1000", + MetadataObject.Type.METALAKE.name(), + "metalake2", + Privilege.Name.USE_CATALOG.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1000", + MetadataObject.Type.TABLE.name(), + "table2", + Privilege.Name.MODIFY_TABLE.name())); + Assertions.assertFalse( + enforcer.enforce( + "user1000", + MetadataObject.Type.TABLE.name(), + "table1", + Privilege.Name.SELECT_TABLE.name())); + } +} diff --git a/server-common/src/test/resources/jcasbin_policy.txt b/server-common/src/test/resources/jcasbin_policy.txt new file mode 100644 index 0000000000..e71a5b6c54 --- /dev/null +++ b/server-common/src/test/resources/jcasbin_policy.txt @@ -0,0 +1,28 @@ +# 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 +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +p, role1, METALAKE, metalake1, OWNER, allow +p, role2, CATALOG, catalog1, OWNER, allow +p, role3, SCHEMA, schema1, OWNER, allow +p, role4, TABLE, table1, OWNER, allow +p, role5, METALAKE, metalake1, SELECT_TABLE, allow +p, role5, METALAKE, metalake1, USE_CATALOG, allow +p, role5, TABLE, table1, SELECT_TABLE, allow + +g, user1, role5 +g, group1, role5 +g, user2, group1 +g, user3, role1 +g, user3, group1
