Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#5077] feat(core): Add the check of user before creating metadata object #5096

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.exceptions;

import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.annotations.FormatString;

/** Exception thrown when a user is forbidden to perform an action. */
public class ForbiddenException extends GravitinoRuntimeException {
/**
* Constructs a new exception with the specified detail message.
*
* @param message the detail message.
* @param args the arguments to the message.
*/
@FormatMethod
public ForbiddenException(@FormatString String message, Object... args) {
super(message, args);
}

/**
* Constructs a new exception with the specified detail message and cause.
*
* @param cause the cause.
* @param message the detail message.
* @param args the arguments to the message.
*/
@FormatMethod
public ForbiddenException(Throwable cause, @FormatString String message, Object... args) {
super(cause, message, args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
import org.apache.gravitino.exceptions.ConnectionFailedException;
import org.apache.gravitino.exceptions.FilesetAlreadyExistsException;
import org.apache.gravitino.exceptions.ForbiddenException;
import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
import org.apache.gravitino.exceptions.IllegalPrivilegeException;
import org.apache.gravitino.exceptions.MetalakeAlreadyExistsException;
Expand Down Expand Up @@ -303,9 +304,13 @@ public void accept(ErrorResponse errorResponse) {

case ErrorConstants.INTERNAL_ERROR_CODE:
throw new RuntimeException(errorMessage);

case ErrorConstants.UNSUPPORTED_OPERATION_CODE:
throw new UnsupportedOperationException(errorMessage);

case ErrorConstants.FORBIDDEN_CODE:
throw new ForbiddenException(errorMessage);

default:
super.accept(errorResponse);
}
Expand Down Expand Up @@ -343,6 +348,9 @@ public void accept(ErrorResponse errorResponse) {
case ErrorConstants.UNSUPPORTED_OPERATION_CODE:
throw new UnsupportedOperationException(errorMessage);

case ErrorConstants.FORBIDDEN_CODE:
throw new ForbiddenException(errorMessage);

case ErrorConstants.INTERNAL_ERROR_CODE:
throw new RuntimeException(errorMessage);

Expand Down Expand Up @@ -380,6 +388,9 @@ public void accept(ErrorResponse errorResponse) {
case ErrorConstants.ALREADY_EXISTS_CODE:
throw new CatalogAlreadyExistsException(errorMessage);

case ErrorConstants.FORBIDDEN_CODE:
throw new ForbiddenException(errorMessage);

case ErrorConstants.INTERNAL_ERROR_CODE:
throw new RuntimeException(errorMessage);

Expand Down Expand Up @@ -495,6 +506,9 @@ public void accept(ErrorResponse errorResponse) {
case ErrorConstants.ALREADY_EXISTS_CODE:
throw new FilesetAlreadyExistsException(errorMessage);

case ErrorConstants.FORBIDDEN_CODE:
throw new ForbiddenException(errorMessage);

case ErrorConstants.INTERNAL_ERROR_CODE:
throw new RuntimeException(errorMessage);

Expand Down Expand Up @@ -530,6 +544,9 @@ public void accept(ErrorResponse errorResponse) {
case ErrorConstants.ALREADY_EXISTS_CODE:
throw new TopicAlreadyExistsException(errorMessage);

case ErrorConstants.FORBIDDEN_CODE:
throw new ForbiddenException(errorMessage);

case ErrorConstants.INTERNAL_ERROR_CODE:
throw new RuntimeException(errorMessage);

Expand Down Expand Up @@ -652,6 +669,9 @@ public void accept(ErrorResponse errorResponse) {
case ErrorConstants.UNSUPPORTED_OPERATION_CODE:
throw new UnsupportedOperationException(errorMessage);

case ErrorConstants.FORBIDDEN_CODE:
throw new ForbiddenException(errorMessage);

case ErrorConstants.INTERNAL_ERROR_CODE:
throw new RuntimeException(errorMessage);

Expand Down
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, This UT only have throw ForbiddenException UT.
I think we need to add normal operation UT.

Copy link
Contributor Author

@jerqi jerqi Oct 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other UTs, we have created the metadata objects. It's ok to add it.

Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
/*
* 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.client.integration.test.authorization;

import static org.apache.gravitino.server.GravitinoServer.WEBSERVER_CONF_PREFIX;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.Collections;
import java.util.Map;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Configs;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.auth.AuthConstants;
import org.apache.gravitino.authorization.Privileges;
import org.apache.gravitino.authorization.SecurableObject;
import org.apache.gravitino.authorization.SecurableObjects;
import org.apache.gravitino.client.GravitinoAdminClient;
import org.apache.gravitino.client.GravitinoMetalake;
import org.apache.gravitino.exceptions.ForbiddenException;
import org.apache.gravitino.file.Fileset;
import org.apache.gravitino.integration.test.container.ContainerSuite;
import org.apache.gravitino.integration.test.container.HiveContainer;
import org.apache.gravitino.integration.test.container.KafkaContainer;
import org.apache.gravitino.integration.test.util.AbstractIT;
import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.types.Types;
import org.apache.gravitino.server.web.JettyServerConfig;
import org.apache.gravitino.utils.RandomNameUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Tag("gravitino-docker-test")
public class CheckCurrentUserIT extends AbstractIT {

private static final Logger LOG = LoggerFactory.getLogger(CheckCurrentUserIT.class);
private static final ContainerSuite containerSuite = ContainerSuite.getInstance();
private static String hmsUri;
private static String kafkaBootstrapServers;
private static GravitinoMetalake metalake;
private static GravitinoMetalake anotherMetalake;
private static String metalakeName = RandomNameUtils.genRandomName("metalake");

@BeforeAll
public static void startIntegrationTest() throws Exception {
Map<String, String> configs = Maps.newHashMap();
configs.put(Configs.ENABLE_AUTHORIZATION.getKey(), String.valueOf(true));
configs.put(Configs.SERVICE_ADMINS.getKey(), AuthConstants.ANONYMOUS_USER);
registerCustomConfigs(configs);
AbstractIT.startIntegrationTest();

containerSuite.startHiveContainer();
hmsUri =
String.format(
"thrift://%s:%d",
containerSuite.getHiveContainer().getContainerIpAddress(),
HiveContainer.HIVE_METASTORE_PORT);

containerSuite.startKafkaContainer();
kafkaBootstrapServers =
String.format(
"%s:%d",
containerSuite.getKafkaContainer().getContainerIpAddress(),
KafkaContainer.DEFAULT_BROKER_PORT);
Comment on lines +80 to +85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to use a Kafka container to test?
If we can use Hive container to test at the same time, we can save resources.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to test the creating topic case.


JettyServerConfig jettyServerConfig =
JettyServerConfig.fromConfig(serverConfig, WEBSERVER_CONF_PREFIX);

String uri = "http://" + jettyServerConfig.getHost() + ":" + jettyServerConfig.getHttpPort();
System.setProperty("user.name", "test");
GravitinoAdminClient anotherClient = GravitinoAdminClient.builder(uri).withSimpleAuth().build();

metalake = client.createMetalake(metalakeName, "metalake comment", Collections.emptyMap());
anotherMetalake = anotherClient.loadMetalake(metalakeName);
}

@AfterAll
public static void tearDown() {
if (client != null) {
client.dropMetalake(metalakeName);
client.close();
client = null;
}

try {
closer.close();
} catch (Exception e) {
LOG.error("Exception in closing CloseableGroup", e);
}
}

@Test
public void testCreateTopic() {
String catalogName = RandomNameUtils.genRandomName("catalogA");

Map<String, String> properties = Maps.newHashMap();
properties.put("bootstrap.servers", kafkaBootstrapServers);

// Test to create catalog with not-existed user
Assertions.assertThrows(
ForbiddenException.class,
() ->
anotherMetalake.createCatalog(
catalogName, Catalog.Type.MESSAGING, "kafka", "comment", properties));

Catalog catalog =
metalake.createCatalog(catalogName, Catalog.Type.MESSAGING, "kafka", "comment", properties);

// Test to create topic with not-existed user
metalake.addUser("test");
Catalog anotherCatalog = anotherMetalake.loadCatalog(catalogName);
metalake.removeUser("test");
NameIdentifier topicIdent = NameIdentifier.of("default", "topic");
Assertions.assertThrows(
ForbiddenException.class,
() ->
anotherCatalog
.asTopicCatalog()
.createTopic(topicIdent, "comment", null, Collections.emptyMap()));

Assertions.assertDoesNotThrow(
() ->
catalog
.asTopicCatalog()
.createTopic(topicIdent, "comment", null, Collections.emptyMap()));
catalog.asTopicCatalog().dropTopic(topicIdent);

metalake.dropCatalog(catalogName);
}

@Test
public void testCreateFileset() {
String catalogName = RandomNameUtils.genRandomName("catalog");
// Test to create a fileset with a not-existed user
Assertions.assertThrows(
ForbiddenException.class,
() ->
anotherMetalake.createCatalog(
catalogName, Catalog.Type.FILESET, "hadoop", "comment", Collections.emptyMap()));

Catalog catalog =
metalake.createCatalog(
catalogName, Catalog.Type.FILESET, "hadoop", "comment", Collections.emptyMap());

// Test to create a schema with a not-existed user
Catalog anotherCatalog = anotherMetalake.loadCatalog(catalogName);
Assertions.assertThrows(
ForbiddenException.class,
() -> anotherCatalog.asSchemas().createSchema("schema", "comment", Collections.emptyMap()));

catalog.asSchemas().createSchema("schema", "comment", Collections.emptyMap());

// Test to create a fileset with a not-existed user
NameIdentifier fileIdent = NameIdentifier.of("schema", "fileset");
Assertions.assertThrows(
ForbiddenException.class,
() ->
anotherCatalog
.asFilesetCatalog()
.createFileset(
fileIdent, "comment", Fileset.Type.EXTERNAL, "tmp", Collections.emptyMap()));

Assertions.assertDoesNotThrow(
() ->
catalog
.asFilesetCatalog()
.createFileset(
fileIdent, "comment", Fileset.Type.EXTERNAL, "tmp", Collections.emptyMap()));

// Clean up
catalog.asFilesetCatalog().dropFileset(fileIdent);
catalog.asSchemas().dropSchema("schema", true);
metalake.dropCatalog(catalogName);
}

@Test
public void testCreateRole() {
SecurableObject metalakeSecObject =
SecurableObjects.ofMetalake(
metalakeName, Lists.newArrayList(Privileges.CreateCatalog.allow()));
Assertions.assertThrows(
ForbiddenException.class,
() ->
anotherMetalake.createRole(
"role", Collections.emptyMap(), Lists.newArrayList(metalakeSecObject)));

Assertions.assertDoesNotThrow(
() ->
metalake.createRole(
"role", Collections.emptyMap(), Lists.newArrayList(metalakeSecObject)));
metalake.deleteRole("role");
}

@Test
public void testCreateTable() {
String catalogName = RandomNameUtils.genRandomName("catalog");
Map<String, String> properties = Maps.newHashMap();
properties.put("metastore.uris", hmsUri);

// Test to create catalog with not-existed user
Assertions.assertThrows(
ForbiddenException.class,
() ->
anotherMetalake.createCatalog(
catalogName, Catalog.Type.RELATIONAL, "hive", "catalog comment", properties));
Catalog catalog =
metalake.createCatalog(
catalogName, Catalog.Type.RELATIONAL, "hive", "catalog comment", properties);

// Test to create schema with not-existed user
Catalog anotherCatalog = anotherMetalake.loadCatalog(catalogName);
Assertions.assertThrows(
ForbiddenException.class,
() -> anotherCatalog.asSchemas().createSchema("schema", "comment", Collections.emptyMap()));

catalog.asSchemas().createSchema("schema", "comment", Collections.emptyMap());

// Test to create table with not-existed user
NameIdentifier tableIdent = NameIdentifier.of("schema", "table");
Assertions.assertThrows(
ForbiddenException.class,
() ->
anotherCatalog
.asTableCatalog()
.createTable(
tableIdent,
new Column[] {
Column.of("col1", Types.IntegerType.get()),
Column.of("col2", Types.StringType.get())
},
"comment",
Collections.emptyMap()));

Assertions.assertDoesNotThrow(
() ->
catalog
.asTableCatalog()
.createTable(
tableIdent,
new Column[] {
Column.of("col1", Types.IntegerType.get()),
Column.of("col2", Types.StringType.get())
},
"comment",
Collections.emptyMap()));

// Clean up
catalog.asTableCatalog().dropTable(tableIdent);
catalog.asSchemas().dropSchema("schema", true);
metalake.dropCatalog(catalogName);
}
}
Loading
Loading