diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java index c53a5adc88f..46a3bb92dce 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java @@ -133,6 +133,19 @@ public String getColumnName() { return getNamePart(3); } + /** + * Retrieves the name from the command line options. + * + * @return The name, or null if not found. + */ + public String getName() { + if (line.hasOption(GravitinoOptions.NAME)) { + return line.getOptionValue(GravitinoOptions.NAME); + } + + return null; + } + /** * Helper method to retrieve a specific part of the full name based on the position of the part. * diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java index 3603a230f28..f0e65dd8649 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java @@ -672,6 +672,7 @@ protected void handleRoleCommand() { FullName name = new FullName(line); String metalake = name.getMetalakeName(); String role = line.getOptionValue(GravitinoOptions.ROLE); + String[] privileges = line.getOptionValues(GravitinoOptions.PRIVILEGE); Command.setAuthenticationMode(auth, userName); @@ -697,6 +698,14 @@ protected void handleRoleCommand() { newDeleteRole(url, ignore, forceDelete, metalake, role).handle(); break; + case CommandActions.GRANT: + newGrantPrivilegesToRole(url, ignore, metalake, role, name, privileges).handle(); + break; + + case CommandActions.REVOKE: + newRevokePrivilegesFromRole(url, ignore, metalake, role, name, privileges).handle(); + break; + default: System.err.println(ErrorMessages.UNSUPPORTED_ACTION); break; diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java index 91993226b92..a42591026a6 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java @@ -45,6 +45,7 @@ public class GravitinoOptions { public static final String OWNER = "owner"; public static final String PARTITION = "partition"; public static final String POSITION = "position"; + public static final String PRIVILEGE = "privilege"; public static final String PROPERTIES = "properties"; public static final String PROPERTY = "property"; public static final String PROVIDER = "provider"; @@ -105,6 +106,7 @@ public Options options() { // Options that support multiple values options.addOption(createArgsOption("p", PROPERTIES, "property name/value pairs")); options.addOption(createArgsOption("t", TAG, "tag name")); + options.addOption(createArgsOption(null, PRIVILEGE, "privilege(s)")); options.addOption(createArgsOption("r", ROLE, "role name")); // Force delete entities and rename metalake operations diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/Privileges.java b/clients/cli/src/main/java/org/apache/gravitino/cli/Privileges.java new file mode 100644 index 00000000000..9d47d8fc9c8 --- /dev/null +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/Privileges.java @@ -0,0 +1,119 @@ +/* + * 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.cli; + +import java.util.HashSet; +import org.apache.gravitino.authorization.Privilege; + +public class Privileges { + public static final String CREATE_CATALOG = "create_catalog"; + public static final String USE_CATALOG = "use_catalog"; + public static final String CREATE_SCHEMA = "create_schema"; + public static final String USE_SCHEMA = "use_schema"; + public static final String CREATE_TABLE = "create_table"; + public static final String MODIFY_TABLE = "modify_table"; + public static final String SELECT_TABLE = "select_table"; + public static final String CREATE_FILESET = "create_fileset"; + public static final String WRITE_FILESET = "write_fileset"; + public static final String READ_FILESET = "read_fileset"; + public static final String CREATE_TOPIC = "create_topic"; + public static final String PRODUCE_TOPIC = "produce_topic"; + public static final String CONSUME_TOPIC = "consume_topic"; + public static final String MANAGE_USERS = "manage_users"; + public static final String CREATE_ROLE = "create_role"; + public static final String MANAGE_GRANTS = "manage_grants"; + + private static final HashSet VALID_PRIVILEGES = new HashSet<>(); + + static { + VALID_PRIVILEGES.add(CREATE_CATALOG); + VALID_PRIVILEGES.add(USE_CATALOG); + VALID_PRIVILEGES.add(CREATE_SCHEMA); + VALID_PRIVILEGES.add(USE_SCHEMA); + VALID_PRIVILEGES.add(CREATE_TABLE); + VALID_PRIVILEGES.add(MODIFY_TABLE); + VALID_PRIVILEGES.add(SELECT_TABLE); + VALID_PRIVILEGES.add(CREATE_FILESET); + VALID_PRIVILEGES.add(WRITE_FILESET); + VALID_PRIVILEGES.add(READ_FILESET); + VALID_PRIVILEGES.add(CREATE_TOPIC); + VALID_PRIVILEGES.add(PRODUCE_TOPIC); + VALID_PRIVILEGES.add(CONSUME_TOPIC); + VALID_PRIVILEGES.add(MANAGE_USERS); + VALID_PRIVILEGES.add(CREATE_ROLE); + VALID_PRIVILEGES.add(MANAGE_GRANTS); + } + + /** + * Checks if a given privilege is a valid one. + * + * @param privilege The privilege to check. + * @return true if the privilege is valid, false otherwise. + */ + public static boolean isValid(String privilege) { + return VALID_PRIVILEGES.contains(privilege); + } + + /** + * Converts a string representation of a privilege to the corresponding {@link Privilege.Name}. + * + * @param privilege the privilege to be converted. + * @return the corresponding {@link Privilege.Name} constant, or nullif the privilege is unknown. + */ + public static Privilege.Name toName(String privilege) { + switch (privilege) { + case CREATE_CATALOG: + return Privilege.Name.CREATE_CATALOG; + case USE_CATALOG: + return Privilege.Name.USE_CATALOG; + case CREATE_SCHEMA: + return Privilege.Name.CREATE_SCHEMA; + case USE_SCHEMA: + return Privilege.Name.USE_SCHEMA; + case CREATE_TABLE: + return Privilege.Name.CREATE_TABLE; + case MODIFY_TABLE: + return Privilege.Name.MODIFY_TABLE; + case SELECT_TABLE: + return Privilege.Name.SELECT_TABLE; + case CREATE_FILESET: + return Privilege.Name.CREATE_FILESET; + case WRITE_FILESET: + return Privilege.Name.WRITE_FILESET; + case READ_FILESET: + return Privilege.Name.READ_FILESET; + case CREATE_TOPIC: + return Privilege.Name.CREATE_TOPIC; + case PRODUCE_TOPIC: + return Privilege.Name.PRODUCE_TOPIC; + case CONSUME_TOPIC: + return Privilege.Name.CONSUME_TOPIC; + case MANAGE_USERS: + return Privilege.Name.MANAGE_USERS; + case CREATE_ROLE: + return Privilege.Name.CREATE_ROLE; + case MANAGE_GRANTS: + return Privilege.Name.MANAGE_GRANTS; + default: + System.err.println("Unknown privilege"); + return null; + } + } +} diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java index 21fa65d994d..a997a95cee3 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java @@ -50,6 +50,7 @@ import org.apache.gravitino.cli.commands.DeleteTopic; import org.apache.gravitino.cli.commands.DeleteUser; import org.apache.gravitino.cli.commands.FilesetDetails; +import org.apache.gravitino.cli.commands.GrantPrivilegesToRole; import org.apache.gravitino.cli.commands.GroupAudit; import org.apache.gravitino.cli.commands.GroupDetails; import org.apache.gravitino.cli.commands.ListAllTags; @@ -85,6 +86,7 @@ import org.apache.gravitino.cli.commands.RemoveTableProperty; import org.apache.gravitino.cli.commands.RemoveTagProperty; import org.apache.gravitino.cli.commands.RemoveTopicProperty; +import org.apache.gravitino.cli.commands.RevokePrivilegesFromRole; import org.apache.gravitino.cli.commands.RoleAudit; import org.apache.gravitino.cli.commands.RoleDetails; import org.apache.gravitino.cli.commands.SchemaAudit; @@ -862,4 +864,24 @@ protected CreateTable newCreateTable( String comment) { return new CreateTable(url, ignore, metalake, catalog, schema, table, columnFile, comment); } + + protected GrantPrivilegesToRole newGrantPrivilegesToRole( + String url, + boolean ignore, + String metalake, + String role, + FullName entity, + String[] privileges) { + return new GrantPrivilegesToRole(url, ignore, metalake, role, entity, privileges); + } + + protected RevokePrivilegesFromRole newRevokePrivilegesFromRole( + String url, + boolean ignore, + String metalake, + String role, + FullName entity, + String[] privileges) { + return new RevokePrivilegesFromRole(url, ignore, metalake, role, entity, privileges); + } } diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/GrantPrivilegesToRole.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/GrantPrivilegesToRole.java new file mode 100644 index 00000000000..e3c9fa4944e --- /dev/null +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/GrantPrivilegesToRole.java @@ -0,0 +1,106 @@ +/* + * 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.cli.commands; + +import java.util.ArrayList; +import java.util.List; +import org.apache.gravitino.MetadataObject; +import org.apache.gravitino.authorization.Privilege; +import org.apache.gravitino.cli.ErrorMessages; +import org.apache.gravitino.cli.FullName; +import org.apache.gravitino.cli.Privileges; +import org.apache.gravitino.client.GravitinoClient; +import org.apache.gravitino.dto.authorization.PrivilegeDTO; +import org.apache.gravitino.exceptions.NoSuchMetadataObjectException; +import org.apache.gravitino.exceptions.NoSuchMetalakeException; +import org.apache.gravitino.exceptions.NoSuchRoleException; + +/** Grants one or more privileges. */ +public class GrantPrivilegesToRole extends MetadataCommand { + + protected final String metalake; + protected final String role; + protected final FullName entity; + protected final String[] privileges; + + /** + * Grants one or more privileges. + * + * @param url The URL of the Gravitino server. + * @param ignoreVersions If true don't check the client/server versions match. + * @param metalake The name of the metalake. + * @param role The name of the role. + * @param entity The name of the entity. + * @param privileges The list of privileges. + */ + public GrantPrivilegesToRole( + String url, + boolean ignoreVersions, + String metalake, + String role, + FullName entity, + String[] privileges) { + super(url, ignoreVersions); + this.metalake = metalake; + this.entity = entity; + this.role = role; + this.privileges = privileges; + } + + /** Grants one or more privileges. */ + @Override + public void handle() { + try { + GravitinoClient client = buildClient(metalake); + List privilegesList = new ArrayList<>(); + + for (String privilege : privileges) { + if (!Privileges.isValid(privilege)) { + System.err.println("Unknown privilege " + privilege); + return; + } + PrivilegeDTO privilegeDTO = + PrivilegeDTO.builder() + .withName(Privileges.toName(privilege)) + .withCondition(Privilege.Condition.ALLOW) + .build(); + privilegesList.add(privilegeDTO); + } + + MetadataObject metadataObject = constructMetadataObject(entity, client); + client.grantPrivilegesToRole(role, metadataObject, privilegesList); + } catch (NoSuchMetalakeException err) { + System.err.println(ErrorMessages.UNKNOWN_METALAKE); + return; + } catch (NoSuchRoleException err) { + System.err.println(ErrorMessages.UNKNOWN_ROLE); + return; + } catch (NoSuchMetadataObjectException err) { + System.err.println(ErrorMessages.UNKNOWN_USER); + return; + } catch (Exception exp) { + System.err.println(exp.getMessage()); + return; + } + + String all = String.join(",", privileges); + System.out.println(role + " granted " + all + " on " + entity.getName()); + } +} diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetadataCommand.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetadataCommand.java new file mode 100644 index 00000000000..3f1e347c1fd --- /dev/null +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetadataCommand.java @@ -0,0 +1,83 @@ +/* + * 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.cli.commands; + +import org.apache.gravitino.Catalog; +import org.apache.gravitino.MetadataObject; +import org.apache.gravitino.MetadataObjects; +import org.apache.gravitino.cli.FullName; +import org.apache.gravitino.client.GravitinoClient; + +public class MetadataCommand extends Command { + + /** + * MetadataCommand constructor. + * + * @param url The URL of the Gravitino server. + * @param ignoreVersions If true don't check the client/server versions match. + */ + public MetadataCommand(String url, boolean ignoreVersions) { + super(url, ignoreVersions); + } + + /** + * Constructs a {@link MetadataObject} based on the provided client, existing metadata object, and + * entity name. + * + * @param entity The name of the entity. + * @param client The Gravitino client. + * @return A MetadataObject of the appropriate type. + * @throws IllegalArgumentException if the entity type cannot be determined or is unknown. + */ + protected MetadataObject constructMetadataObject(FullName entity, GravitinoClient client) { + + MetadataObject metadataObject; + String name = entity.getName(); + + if (entity.hasColumnName()) { + metadataObject = MetadataObjects.of(null, name, MetadataObject.Type.COLUMN); + } else if (entity.hasTableName()) { + Catalog catalog = client.loadCatalog(entity.getCatalogName()); + Catalog.Type catalogType = catalog.type(); + if (catalogType == Catalog.Type.RELATIONAL) { + metadataObject = MetadataObjects.of(null, name, MetadataObject.Type.TABLE); + } else if (catalogType == Catalog.Type.MESSAGING) { + metadataObject = MetadataObjects.of(null, name, MetadataObject.Type.TOPIC); + } else if (catalogType == Catalog.Type.FILESET) { + metadataObject = MetadataObjects.of(null, name, MetadataObject.Type.FILESET); + } else { + throw new IllegalArgumentException("Unknown entity type: " + name); + } + } else if (entity.hasSchemaName()) { + metadataObject = MetadataObjects.of(null, name, MetadataObject.Type.SCHEMA); + } else if (entity.hasCatalogName()) { + metadataObject = MetadataObjects.of(null, name, MetadataObject.Type.CATALOG); + } else if (entity.getMetalakeName() != null) { + metadataObject = MetadataObjects.of(null, name, MetadataObject.Type.METALAKE); + } else { + throw new IllegalArgumentException("Unknown entity type: " + name); + } + return metadataObject; + } + + /* Do nothing, as parent will override. */ + @Override + public void handle() {} +} diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RevokePrivilegesFromRole.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RevokePrivilegesFromRole.java new file mode 100644 index 00000000000..8077532319e --- /dev/null +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RevokePrivilegesFromRole.java @@ -0,0 +1,106 @@ +/* + * 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.cli.commands; + +import java.util.ArrayList; +import java.util.List; +import org.apache.gravitino.MetadataObject; +import org.apache.gravitino.authorization.Privilege; +import org.apache.gravitino.cli.ErrorMessages; +import org.apache.gravitino.cli.FullName; +import org.apache.gravitino.cli.Privileges; +import org.apache.gravitino.client.GravitinoClient; +import org.apache.gravitino.dto.authorization.PrivilegeDTO; +import org.apache.gravitino.exceptions.NoSuchMetadataObjectException; +import org.apache.gravitino.exceptions.NoSuchMetalakeException; +import org.apache.gravitino.exceptions.NoSuchRoleException; + +/** Revokes one or more privileges. */ +public class RevokePrivilegesFromRole extends MetadataCommand { + + protected final String metalake; + protected final String role; + protected final FullName entity; + protected final String[] privileges; + + /** + * Revokes one or more privileges. + * + * @param url The URL of the Gravitino server. + * @param ignoreVersions If true don't check the client/server versions match. + * @param metalake The name of the metalake. + * @param role The name of the role. + * @param entity The name of the entity. + * @param privileges The list of privileges. + */ + public RevokePrivilegesFromRole( + String url, + boolean ignoreVersions, + String metalake, + String role, + FullName entity, + String[] privileges) { + super(url, ignoreVersions); + this.metalake = metalake; + this.entity = entity; + this.role = role; + this.privileges = privileges; + } + + /** Revokes One or more privileges. */ + @Override + public void handle() { + try { + GravitinoClient client = buildClient(metalake); + List privilegesList = new ArrayList<>(); + + for (String privilege : privileges) { + if (!Privileges.isValid(privilege)) { + System.err.println("Unknown privilege " + privilege); + return; + } + PrivilegeDTO privilegeDTO = + PrivilegeDTO.builder() + .withName(Privileges.toName(privilege)) + .withCondition(Privilege.Condition.DENY) + .build(); + privilegesList.add(privilegeDTO); + } + + MetadataObject metadataObject = constructMetadataObject(entity, client); + client.revokePrivilegesFromRole(role, metadataObject, privilegesList); + } catch (NoSuchMetalakeException err) { + System.err.println(ErrorMessages.UNKNOWN_METALAKE); + return; + } catch (NoSuchRoleException err) { + System.err.println(ErrorMessages.UNKNOWN_ROLE); + return; + } catch (NoSuchMetadataObjectException err) { + System.err.println(ErrorMessages.UNKNOWN_USER); + return; + } catch (Exception exp) { + System.err.println(exp.getMessage()); + return; + } + + String all = String.join(",", privileges); + System.out.println(role + " revoked " + all + " on " + entity.getName()); + } +} diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RoleDetails.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RoleDetails.java index 613ee60d2ce..2c1613eded7 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RoleDetails.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RoleDetails.java @@ -20,7 +20,7 @@ package org.apache.gravitino.cli.commands; import java.util.List; -import java.util.stream.Collectors; +import org.apache.gravitino.authorization.Privilege; import org.apache.gravitino.authorization.SecurableObject; import org.apache.gravitino.cli.ErrorMessages; import org.apache.gravitino.client.GravitinoClient; @@ -65,9 +65,12 @@ public void handle() { return; } - // TODO expand in securable objects PR - String all = objects.stream().map(SecurableObject::name).collect(Collectors.joining(",")); - - System.out.println(all.toString()); + for (SecurableObject object : objects) { + System.out.print(object.name() + "," + object.type() + ","); + for (Privilege privilege : object.privileges()) { + System.out.print(privilege.simpleString() + " "); + } + } + System.out.println(""); } } diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestPrivileges.java b/clients/cli/src/test/java/org/apache/gravitino/cli/TestPrivileges.java new file mode 100644 index 00000000000..b6d39caded5 --- /dev/null +++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestPrivileges.java @@ -0,0 +1,52 @@ +/* + * 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.cli; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class TestsPrivileges { + + @Test + void testValidPrivilege() { + assertTrue(Privileges.isValid(Privileges.CREATE_CATALOG)); + assertTrue(Privileges.isValid(Privileges.CREATE_TABLE)); + assertTrue(Privileges.isValid(Privileges.CONSUME_TOPIC)); + assertTrue(Privileges.isValid(Privileges.MANAGE_GRANTS)); + } + + @Test + void testInvalidPrivilege() { + assertFalse(Privileges.isValid("non_existent_privilege")); + assertFalse(Privileges.isValid("create_database")); + } + + @Test + void testNullPrivilege() { + assertFalse(Privileges.isValid(null)); + } + + @Test + void testEmptyPrivilege() { + assertFalse(Privileges.isValid("")); + } +} diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestRoleCommands.java b/clients/cli/src/test/java/org/apache/gravitino/cli/TestRoleCommands.java index 179dba14fe8..88b380d63ee 100644 --- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestRoleCommands.java +++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestRoleCommands.java @@ -19,7 +19,9 @@ package org.apache.gravitino.cli; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -29,7 +31,9 @@ import org.apache.commons.cli.Options; import org.apache.gravitino.cli.commands.CreateRole; import org.apache.gravitino.cli.commands.DeleteRole; +import org.apache.gravitino.cli.commands.GrantPrivilegesToRole; import org.apache.gravitino.cli.commands.ListRoles; +import org.apache.gravitino.cli.commands.RevokePrivilegesFromRole; import org.apache.gravitino.cli.commands.RoleAudit; import org.apache.gravitino.cli.commands.RoleDetails; import org.junit.jupiter.api.BeforeEach; @@ -152,4 +156,62 @@ void testDeleteRoleForceCommand() { commandLine.handleCommandLine(); verify(mockDelete).handle(); } + + @Test + void testGrantPrivilegesToRole() { + GrantPrivilegesToRole mockGrant = mock(GrantPrivilegesToRole.class); + String[] privileges = {"create_table", "modify_table"}; + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog"); + when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin"); + when(mockCommandLine.hasOption(GravitinoOptions.PRIVILEGE)).thenReturn(true); + when(mockCommandLine.getOptionValues(GravitinoOptions.PRIVILEGE)).thenReturn(privileges); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.ROLE, CommandActions.GRANT)); + doReturn(mockGrant) + .when(commandLine) + .newGrantPrivilegesToRole( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + eq("admin"), + any(), + eq(privileges)); + commandLine.handleCommandLine(); + verify(mockGrant).handle(); + } + + @Test + void testRevokePrivilegesFromRole() { + RevokePrivilegesFromRole mockRevoke = mock(RevokePrivilegesFromRole.class); + String[] privileges = {"create_table", "modify_table"}; + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog"); + when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin"); + when(mockCommandLine.hasOption(GravitinoOptions.PRIVILEGE)).thenReturn(true); + when(mockCommandLine.getOptionValues(GravitinoOptions.PRIVILEGE)).thenReturn(privileges); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.ROLE, CommandActions.REVOKE)); + doReturn(mockRevoke) + .when(commandLine) + .newRevokePrivilegesFromRole( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + eq("admin"), + any(), + eq(privileges)); + commandLine.handleCommandLine(); + verify(mockRevoke).handle(); + } } diff --git a/docs/cli.md b/docs/cli.md index b01ea477562..e6e2f5aa609 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -672,6 +672,12 @@ gcli catalog set --owner --group groupA --name postgres ### Role commands +When granting or revoking privileges the following privileges can be used. + +create_catalog, use_catalog, create_schema, use_schema, create_table, modify_table, select_table, create_fileset, write_fileset, read_fileset, create_topic, produce_topic, consume_topic, manage_users, create_role, manage_grants + +Note that some are only valid for certain entities. + #### Display role details ```bash @@ -721,10 +727,23 @@ gcli group grant --group groupA --role admin ``` #### Remove a role from a group + ```bash gcli group revoke --group groupA --role admin ``` +### Grant a privilege + +```bash +gcli role grant --name catalog_postgres --role admin --privilege create_table modify_table +``` + +### Revoke a privilege + +```bash +gcli role revoke --metalake metalake_demo --name catalog_postgres --role admin --privilege create_table modify_table +``` + ### Topic commands #### Display a topic's details