Skip to content

Commit

Permalink
[apache#2238] feat(server): Add the operations for the user
Browse files Browse the repository at this point in the history
This reverts commit d6cef11.
  • Loading branch information
Heng Qin committed Mar 29, 2024
1 parent 6641d70 commit a74d02c
Show file tree
Hide file tree
Showing 9 changed files with 726 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright 2024 Datastrato Pvt Ltd.
* This software is licensed under the Apache License version 2.
*/
package com.datastrato.gravitino.dto.authorization;

import com.datastrato.gravitino.Audit;
import com.datastrato.gravitino.authorization.User;
import com.datastrato.gravitino.dto.AuditDTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;

import java.util.List;

import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nullable;

/** Represents a User Data Transfer Object (DTO). */
public class UserDTO implements User {

@JsonProperty("name")
private String name;


@JsonProperty("audit")
private AuditDTO audit;

@Nullable
@JsonProperty("roles")
private List<String> roles;

/** Default constructor for Jackson deserialization. */
protected UserDTO() {}

/**
* Creates a new instance of UserDTO.
*
* @param name The name of the User DTO.
* @param audit The audit information of the User DTO.
*/
protected UserDTO(String name, AuditDTO audit) {
this.name = name;
this.audit = audit;
}

/** @return The name of the User DTO. */
@Override
public String name() {
return name;
}

/**
* The roles of the user. A user can have multiple roles. Every role binds several privileges.
*
* @return The roles of the user.
*/
@Override
public List<String> roles() {
return roles;
}

/** @return The audit information of the User DTO. */
@Override
public Audit auditInfo() {
return audit;
}

/**
* Creates a new Builder for constructing an User DTO.
*
* @return A new Builder instance.
*/
public static Builder builder() {
return new Builder();
}

/**
* Builder class for constructing a UserDTO instance.
*
* @param <S> The type of the builder instance.
*/
public static class Builder<S extends Builder> {

/** The name of the user. */
protected String name;

/** The roles of the user. */
protected List<String> roles;

/** The audit information of the user. */
protected AuditDTO audit;


/**
* Sets the name of the user.
*
* @param name The name of the user.
* @return The builder instance.
*/
public S withName(String name) {
this.name = name;
return (S) this;
}

/**
* Sets the properties of the user.
*
* @param roles The roles of the user.
* @return The builder instance.
*/
public S withRoles(List<String> roles) {
this.roles = roles;
return (S) this;
}

/**
* Sets the audit information of the user.
*
* @param audit The audit information of the user.
* @return The builder instance.
*/
public S withAudit(AuditDTO audit) {
this.audit = audit;
return (S) this;
}

/**
* Builds an instance of UserDTO using the builder's properties.
*
* @return An instance of UserDTO.
* @throws IllegalArgumentException If the name or audit are not set.
*/
public UserDTO build() {
Preconditions.checkArgument(StringUtils.isNotBlank(name), "name cannot be null or empty");
Preconditions.checkArgument(audit != null, "audit cannot be null");
return new UserDTO(name, audit);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2024 Datastrato Pvt Ltd.
* This software is licensed under the Apache License version 2.
*/
package com.datastrato.gravitino.dto.requests;

import com.datastrato.gravitino.rest.RESTRequest;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.jackson.Jacksonized;
import org.apache.commons.lang3.StringUtils;

/** Represents a request to create a user. */
@Getter
@EqualsAndHashCode
@ToString
@Builder
@Jacksonized
public class UserAddRequest implements RESTRequest {

@JsonProperty("name")
private final String name;

/** Default constructor for MetalakeCreateRequest. (Used for Jackson deserialization.) */
public UserAddRequest() {
this(null);
}

/**
* Creates a new UserCreateRequest.
*
* @param name The name of the user.
*/
public UserAddRequest(String name) {
super();
this.name = name;
}

/**
* Validates the {@link UserAddRequest} request.
*
* @throws IllegalArgumentException If the request is invalid, this exception is thrown.
*/
@Override
public void validate() throws IllegalArgumentException {
Preconditions.checkArgument(
StringUtils.isNotBlank(name), "\"name\" field is required and cannot be empty");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2024 Datastrato Pvt Ltd.
* This software is licensed under the Apache License version 2.
*/
package com.datastrato.gravitino.dto.responses;

import com.datastrato.gravitino.dto.authorization.UserDTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;

/** Represents a response for a user. */
@Getter
@ToString
@EqualsAndHashCode(callSuper = true)
public class UserResponse extends BaseResponse {

@JsonProperty("user")
private final UserDTO user;

/**
* Constructor for UserResponse.
*
* @param user The user data transfer object.
*/
public UserResponse(UserDTO user) {
super(0);
this.user = user;
}

/** Default constructor for UserResponse. (Used for Jackson deserialization.) */
public UserResponse() {
super();
this.user = null;
}

/**
* Validates the response data.
*
* @throws IllegalArgumentException if the name or audit is not set.
*/
@Override
public void validate() throws IllegalArgumentException {
super.validate();

Preconditions.checkArgument(user != null, "user must not be null");
Preconditions.checkArgument(
StringUtils.isNotBlank(user.name()), "user 'name' must not be null and empty");
Preconditions.checkArgument(user.auditInfo() != null, "user 'auditInfo' must not be null");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import com.datastrato.gravitino.Audit;
import com.datastrato.gravitino.Catalog;
import com.datastrato.gravitino.Metalake;
import com.datastrato.gravitino.authorization.User;
import com.datastrato.gravitino.dto.AuditDTO;
import com.datastrato.gravitino.dto.CatalogDTO;
import com.datastrato.gravitino.dto.MetalakeDTO;
import com.datastrato.gravitino.dto.authorization.UserDTO;
import com.datastrato.gravitino.dto.file.FilesetDTO;
import com.datastrato.gravitino.dto.rel.ColumnDTO;
import com.datastrato.gravitino.dto.rel.DistributionDTO;
Expand Down Expand Up @@ -327,6 +329,24 @@ public static IndexDTO toDTO(Index index) {
.build();
}

/**
* Converts a user implementation to a UserDTO.
*
* @param user The user implementation.
* @return The user DTO.
*/
public static UserDTO toDTO(User user) {
if (user instanceof UserDTO) {
return (UserDTO) user;
}

return UserDTO.builder()
.withName(user.name())
.withRoles(user.roles())
.withAudit(toDTO(user.auditInfo()))
.build();
}

/**
* Converts a Expression to an FunctionArg DTO.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.datastrato.gravitino.dto.AuditDTO;
import com.datastrato.gravitino.dto.CatalogDTO;
import com.datastrato.gravitino.dto.MetalakeDTO;
import com.datastrato.gravitino.dto.authorization.UserDTO;
import com.datastrato.gravitino.dto.rel.ColumnDTO;
import com.datastrato.gravitino.dto.rel.SchemaDTO;
import com.datastrato.gravitino.dto.rel.TableDTO;
Expand Down Expand Up @@ -223,4 +224,19 @@ void testOAuthErrorException() throws IllegalArgumentException {
OAuth2ErrorResponse response = new OAuth2ErrorResponse();
assertThrows(IllegalArgumentException.class, () -> response.validate());
}

@Test
void testUserResponse() throws IllegalArgumentException {
AuditDTO audit =
new AuditDTO.Builder().withCreator("creator").withCreateTime(Instant.now()).build();
UserDTO user = UserDTO.builder().withName("user1").withAudit(audit).build();
UserResponse response = new UserResponse(user);
response.validate(); // No exception thrown
}

@Test
void testUserResponseException() throws IllegalArgumentException {
UserResponse user = new UserResponse();
assertThrows(IllegalArgumentException.class, () -> user.validate());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package com.datastrato.gravitino.server;

import com.datastrato.gravitino.GravitinoEnv;
import com.datastrato.gravitino.authorization.AccessControlManager;
import com.datastrato.gravitino.catalog.CatalogManager;
import com.datastrato.gravitino.catalog.CatalogOperationDispatcher;
import com.datastrato.gravitino.metalake.MetalakeManager;
Expand Down Expand Up @@ -73,6 +74,7 @@ private void initializeRestApi() {
protected void configure() {
bind(gravitinoEnv.metalakesManager()).to(MetalakeManager.class).ranked(1);
bind(gravitinoEnv.catalogManager()).to(CatalogManager.class).ranked(1);
bind(gravitinoEnv.accessControlManager()).to(AccessControlManager.class).ranked(1);
bind(gravitinoEnv.catalogOperationDispatcher())
.to(CatalogOperationDispatcher.class)
.ranked(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.datastrato.gravitino.exceptions.PartitionAlreadyExistsException;
import com.datastrato.gravitino.exceptions.SchemaAlreadyExistsException;
import com.datastrato.gravitino.exceptions.TableAlreadyExistsException;
import com.datastrato.gravitino.exceptions.UserAlreadyExistsException;
import com.datastrato.gravitino.server.web.Utils;
import com.google.common.annotations.VisibleForTesting;
import javax.ws.rs.core.Response;
Expand Down Expand Up @@ -55,6 +56,11 @@ public static Response handleFilesetException(
return FilesetExceptionHandler.INSTANCE.handle(op, fileset, schema, e);
}

public static Response handleUserException(
OperationType op, String user, String metalake, Exception e) {
return UserExceptionHandler.INSTANCE.handle(op, user, metalake, e);
}

private static class PartitionExceptionHandler extends BaseExceptionHandler {

private static final ExceptionHandler INSTANCE = new PartitionExceptionHandler();
Expand Down Expand Up @@ -254,6 +260,38 @@ public Response handle(OperationType op, String fileset, String schema, Exceptio
}
}

private static class UserExceptionHandler extends BaseExceptionHandler {

private static final ExceptionHandler INSTANCE = new UserExceptionHandler();

private static String getUserErrorMsg(
String fileset, String operation, String metalake, String reason) {
return String.format(
"Failed to operate user %s operation [%s] under metalake [%s], reason [%s]",
fileset, operation, metalake, reason);
}

@Override
public Response handle(OperationType op, String user, String metalake, Exception e) {
String formatted = StringUtil.isBlank(user) ? "" : " [" + user + "]";
String errorMsg = getUserErrorMsg(formatted, op.name(), metalake, getErrorMsg(e));
LOG.warn(errorMsg, e);

if (e instanceof IllegalArgumentException) {
return Utils.illegalArguments(errorMsg, e);

} else if (e instanceof NotFoundException) {
return Utils.notFound(errorMsg, e);

} else if (e instanceof UserAlreadyExistsException) {
return Utils.alreadyExists(errorMsg, e);

} else {
return super.handle(op, user, metalake, e);
}
}
}

@VisibleForTesting
static class BaseExceptionHandler extends ExceptionHandler {

Expand Down
Loading

0 comments on commit a74d02c

Please sign in to comment.