This is an automated email from the ASF dual-hosted git repository.
shaofengshi 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 7ad9377d0 [#5162] Add grant and revoke privileges to the Gravitino
CLI. (#5783)
7ad9377d0 is described below
commit 7ad9377d0beeb06c92149c7295970766a826ca57
Author: Justin Mclean <[email protected]>
AuthorDate: Thu Dec 19 23:32:50 2024 +1100
[#5162] Add grant and revoke privileges to the Gravitino CLI. (#5783)
### What changes were proposed in this pull request?
Add grant and revoke privileges to the Gravitino CLI.
### Why are the changes needed?
To complete the role commands.
Fix: #5162
### Does this PR introduce _any_ user-facing change?
No but it adds two more commands.
### How was this patch tested?
Tested locally.
---
.../java/org/apache/gravitino/cli/FullName.java | 13 +++
.../apache/gravitino/cli/GravitinoCommandLine.java | 9 ++
.../org/apache/gravitino/cli/GravitinoOptions.java | 2 +
.../java/org/apache/gravitino/cli/Privileges.java | 119 +++++++++++++++++++++
.../apache/gravitino/cli/TestableCommandLine.java | 22 ++++
.../cli/commands/GrantPrivilegesToRole.java | 106 ++++++++++++++++++
.../gravitino/cli/commands/MetadataCommand.java | 83 ++++++++++++++
.../cli/commands/RevokePrivilegesFromRole.java | 106 ++++++++++++++++++
.../apache/gravitino/cli/commands/RoleDetails.java | 13 ++-
.../org/apache/gravitino/cli/TestPrivileges.java | 52 +++++++++
.../org/apache/gravitino/cli/TestRoleCommands.java | 62 +++++++++++
docs/cli.md | 19 ++++
12 files changed, 601 insertions(+), 5 deletions(-)
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 c53a5adc8..46a3bb92d 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 class FullName {
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 3603a230f..f0e65dd86 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 @@ public class GravitinoCommandLine extends
TestableCommandLine {
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 @@ public class GravitinoCommandLine extends
TestableCommandLine {
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 91993226b..a42591026 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 class GravitinoOptions {
// 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 000000000..9d47d8fc9
--- /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<String> 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 21fa65d99..a997a95ce 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.DeleteTag;
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.RemoveSchemaProperty;
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 @@ public class TestableCommandLine {
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 000000000..e3c9fa494
--- /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<Privilege> 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 000000000..3f1e347c1
--- /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 000000000..807753231
--- /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<Privilege> 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 613ee60d2..2c1613ede 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 class RoleDetails extends Command {
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 000000000..b6d39cade
--- /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 179dba14f..88b380d63 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.CommandLine;
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 @@ class TestRoleCommands {
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 b01ea4775..e6e2f5aa6 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