mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-13 00:22:23 +00:00
Add Role entity (#1970)
This commit is contained in:
parent
ab4c9ede25
commit
485e661430
15
bootstrap/sql/mysql/v004__create_db_connection_info.sql
Normal file
15
bootstrap/sql/mysql/v004__create_db_connection_info.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE TABLE IF NOT EXISTS role_entity (
|
||||
id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL,
|
||||
name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL,
|
||||
json JSON NOT NULL,
|
||||
updatedAt TIMESTAMP GENERATED ALWAYS AS (TIMESTAMP(STR_TO_DATE(json ->> '$.updatedAt', '%Y-%m-%dT%T.%fZ'))) NOT NULL,
|
||||
updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.updatedBy') NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY unique_name(name),
|
||||
INDEX (updatedBy),
|
||||
INDEX (updatedAt)
|
||||
);
|
||||
|
||||
ALTER TABLE role_entity
|
||||
ADD COLUMN deleted BOOLEAN GENERATED ALWAYS AS (JSON_EXTRACT(json, '$.deleted')),
|
||||
ADD INDEX (deleted);
|
||||
@ -68,8 +68,9 @@ public final class Entity {
|
||||
public static final String POLICY = "policy";
|
||||
|
||||
//
|
||||
// Team/user
|
||||
// Role, team and user
|
||||
//
|
||||
public static final String ROLE = "role";
|
||||
public static final String USER = "user";
|
||||
public static final String TEAM = "team";
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@ import org.openmetadata.catalog.entity.services.DatabaseService;
|
||||
import org.openmetadata.catalog.entity.services.MessagingService;
|
||||
import org.openmetadata.catalog.entity.services.PipelineService;
|
||||
import org.openmetadata.catalog.entity.services.StorageService;
|
||||
import org.openmetadata.catalog.entity.teams.Role;
|
||||
import org.openmetadata.catalog.entity.teams.Team;
|
||||
import org.openmetadata.catalog.entity.teams.User;
|
||||
import org.openmetadata.catalog.jdbi3.BotsRepository.BotsEntityInterface;
|
||||
@ -62,6 +63,7 @@ import org.openmetadata.catalog.jdbi3.PipelineRepository.PipelineEntityInterface
|
||||
import org.openmetadata.catalog.jdbi3.PipelineServiceRepository.PipelineServiceEntityInterface;
|
||||
import org.openmetadata.catalog.jdbi3.PolicyRepository.PolicyEntityInterface;
|
||||
import org.openmetadata.catalog.jdbi3.ReportRepository.ReportEntityInterface;
|
||||
import org.openmetadata.catalog.jdbi3.RoleRepository.RoleEntityInterface;
|
||||
import org.openmetadata.catalog.jdbi3.StorageServiceRepository.StorageServiceEntityInterface;
|
||||
import org.openmetadata.catalog.jdbi3.TableRepository.TableEntityInterface;
|
||||
import org.openmetadata.catalog.jdbi3.TeamRepository.TeamEntityInterface;
|
||||
@ -89,6 +91,9 @@ public interface CollectionDAO {
|
||||
@CreateSqlObject
|
||||
EntityExtensionDAO entityExtensionDAO();
|
||||
|
||||
@CreateSqlObject
|
||||
RoleDAO roleDAO();
|
||||
|
||||
@CreateSqlObject
|
||||
UserDAO userDAO();
|
||||
|
||||
@ -894,6 +899,28 @@ public interface CollectionDAO {
|
||||
}
|
||||
}
|
||||
|
||||
interface RoleDAO extends EntityDAO<Role> {
|
||||
@Override
|
||||
default String getTableName() {
|
||||
return "role_entity";
|
||||
}
|
||||
|
||||
@Override
|
||||
default Class<Role> getEntityClass() {
|
||||
return Role.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getNameColumn() {
|
||||
return "name";
|
||||
}
|
||||
|
||||
@Override
|
||||
default EntityReference getEntityReference(Role entity) {
|
||||
return new RoleEntityInterface(entity).getEntityReference();
|
||||
}
|
||||
}
|
||||
|
||||
interface TeamDAO extends EntityDAO<Team> {
|
||||
@Override
|
||||
default String getTableName() {
|
||||
|
||||
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed 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.openmetadata.catalog.jdbi3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.openmetadata.catalog.Entity;
|
||||
import org.openmetadata.catalog.entity.teams.Role;
|
||||
import org.openmetadata.catalog.exception.CatalogExceptionMessage;
|
||||
import org.openmetadata.catalog.resources.teams.RoleResource;
|
||||
import org.openmetadata.catalog.type.ChangeDescription;
|
||||
import org.openmetadata.catalog.type.EntityReference;
|
||||
import org.openmetadata.catalog.type.TagLabel;
|
||||
import org.openmetadata.catalog.util.EntityInterface;
|
||||
import org.openmetadata.catalog.util.EntityUtil.Fields;
|
||||
import org.openmetadata.catalog.util.JsonUtils;
|
||||
|
||||
public class RoleRepository extends EntityRepository<Role> {
|
||||
static final Fields ROLE_UPDATE_FIELDS = new Fields(RoleResource.FIELD_LIST, null);
|
||||
static final Fields ROLE_PATCH_FIELDS = new Fields(RoleResource.FIELD_LIST, null);
|
||||
private final CollectionDAO dao;
|
||||
|
||||
public RoleRepository(CollectionDAO dao) {
|
||||
super(
|
||||
RoleResource.COLLECTION_PATH,
|
||||
Entity.ROLE,
|
||||
Role.class,
|
||||
dao.roleDAO(),
|
||||
dao,
|
||||
ROLE_PATCH_FIELDS,
|
||||
ROLE_UPDATE_FIELDS);
|
||||
this.dao = dao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role setFields(Role role, Fields fields) throws IOException {
|
||||
// Nothing to set.
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restorePatchAttributes(Role original, Role updated) {
|
||||
// Patch can't make changes to following fields. Ignore the changes
|
||||
updated.withName(original.getName()).withId(original.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityInterface<Role> getEntityInterface(Role entity) {
|
||||
return new RoleEntityInterface(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Role role) throws IOException {}
|
||||
|
||||
@Override
|
||||
public void storeEntity(Role role, boolean update) throws IOException {
|
||||
// Don't store href as JSON. Build it on the fly based on relationships
|
||||
role.withHref(null);
|
||||
|
||||
if (update) {
|
||||
dao.roleDAO().update(role.getId(), JsonUtils.pojoToJson(role));
|
||||
} else {
|
||||
dao.roleDAO().insert(role);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeRelationships(Role role) {}
|
||||
|
||||
@Override
|
||||
public EntityUpdater getUpdater(Role original, Role updated, boolean patchOperation) {
|
||||
return new RoleUpdater(original, updated, patchOperation);
|
||||
}
|
||||
|
||||
public static class RoleEntityInterface implements EntityInterface<Role> {
|
||||
private final Role entity;
|
||||
|
||||
public RoleEntityInterface(Role entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return entity.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return entity.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityReference getOwner() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullyQualifiedName() {
|
||||
return entity.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TagLabel> getTags() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getVersion() {
|
||||
return entity.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUpdatedBy() {
|
||||
return entity.getUpdatedBy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getUpdatedAt() {
|
||||
return entity.getUpdatedAt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getHref() {
|
||||
return entity.getHref();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EntityReference> getFollowers() {
|
||||
throw new UnsupportedOperationException("Role does not support followers");
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityReference getEntityReference() {
|
||||
return new EntityReference()
|
||||
.withId(getId())
|
||||
.withName(getFullyQualifiedName())
|
||||
.withDescription(getDescription())
|
||||
.withDisplayName(getDisplayName())
|
||||
.withType(Entity.ROLE)
|
||||
.withHref(getHref());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(UUID id) {
|
||||
entity.setId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
entity.setDescription(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(String displayName) {
|
||||
entity.setDisplayName(displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpdateDetails(String updatedBy, Date updatedAt) {
|
||||
entity.setUpdatedBy(updatedBy);
|
||||
entity.setUpdatedAt(updatedAt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangeDescription(Double newVersion, ChangeDescription changeDescription) {
|
||||
entity.setVersion(newVersion);
|
||||
entity.setChangeDescription(changeDescription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwner(EntityReference owner) {}
|
||||
|
||||
@Override
|
||||
public void setDeleted(boolean flag) {
|
||||
entity.setDeleted(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role withHref(URI href) {
|
||||
return entity.withHref(href);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeDescription getChangeDescription() {
|
||||
return entity.getChangeDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTags(List<TagLabel> tags) {}
|
||||
}
|
||||
|
||||
/** Handles entity updated from PUT and POST operation. */
|
||||
public class RoleUpdater extends EntityUpdater {
|
||||
public RoleUpdater(Role original, Role updated, boolean patchOperation) {
|
||||
super(original, updated, patchOperation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entitySpecificUpdate() throws IOException {
|
||||
// Update operation cannot undelete a role.
|
||||
if (updated.getEntity().getDeleted() != original.getEntity().getDeleted()) {
|
||||
throw new IllegalArgumentException(CatalogExceptionMessage.readOnlyAttribute("Role", "deleted"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed 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.openmetadata.catalog.resources.teams;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.dropwizard.jersey.PATCH;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.v3.oas.annotations.ExternalDocumentation;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import javax.json.JsonPatch;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.openmetadata.catalog.api.teams.CreateRole;
|
||||
import org.openmetadata.catalog.entity.teams.Role;
|
||||
import org.openmetadata.catalog.jdbi3.CollectionDAO;
|
||||
import org.openmetadata.catalog.jdbi3.RoleRepository;
|
||||
import org.openmetadata.catalog.resources.Collection;
|
||||
import org.openmetadata.catalog.security.Authorizer;
|
||||
import org.openmetadata.catalog.security.SecurityUtil;
|
||||
import org.openmetadata.catalog.type.EntityHistory;
|
||||
import org.openmetadata.catalog.util.EntityUtil;
|
||||
import org.openmetadata.catalog.util.RestUtil;
|
||||
import org.openmetadata.catalog.util.RestUtil.PatchResponse;
|
||||
import org.openmetadata.catalog.util.ResultList;
|
||||
|
||||
@Path("/v1/roles")
|
||||
@Api(value = "Roles collection", tags = "Roles collection")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Collection(name = "roles")
|
||||
public class RoleResource {
|
||||
public static final String COLLECTION_PATH = "/v1/roles/";
|
||||
private final RoleRepository dao;
|
||||
private final Authorizer authorizer;
|
||||
|
||||
@Inject
|
||||
public RoleResource(CollectionDAO dao, Authorizer authorizer) {
|
||||
Objects.requireNonNull(dao, "RoleRepository must not be null");
|
||||
this.dao = new RoleRepository(dao);
|
||||
this.authorizer = authorizer;
|
||||
}
|
||||
|
||||
public static class RoleList extends ResultList<Role> {
|
||||
@SuppressWarnings("unused") /* Required for tests */
|
||||
RoleList() {}
|
||||
|
||||
public RoleList(List<Role> roles, String beforeCursor, String afterCursor, int total)
|
||||
throws GeneralSecurityException, UnsupportedEncodingException {
|
||||
super(roles, beforeCursor, afterCursor, total);
|
||||
}
|
||||
}
|
||||
|
||||
// No additional fields supported for role entity at the moment.
|
||||
public static final List<String> FIELD_LIST = new ArrayList<>();
|
||||
|
||||
@GET
|
||||
@Valid
|
||||
@Operation(
|
||||
summary = "List roles",
|
||||
tags = "roles",
|
||||
description =
|
||||
"Get a list of roles. Use cursor-based pagination to limit the number of entries in the list using `limit`"
|
||||
+ " and `before` or `after` query params.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "List of roles",
|
||||
content = @Content(mediaType = "application/json", schema = @Schema(implementation = RoleList.class)))
|
||||
})
|
||||
public ResultList<Role> list(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Parameter(description = "Limit the number tables returned. (1 to 1000000, default = 10)")
|
||||
@DefaultValue("10")
|
||||
@Min(1)
|
||||
@Max(1000000)
|
||||
@QueryParam("limit")
|
||||
int limitParam,
|
||||
@Parameter(description = "Returns list of tables before this cursor", schema = @Schema(type = "string"))
|
||||
@QueryParam("before")
|
||||
String before,
|
||||
@Parameter(description = "Returns list of tables after this cursor", schema = @Schema(type = "string"))
|
||||
@QueryParam("after")
|
||||
String after)
|
||||
throws IOException, GeneralSecurityException, ParseException {
|
||||
RestUtil.validateCursors(before, after);
|
||||
EntityUtil.Fields fields = new EntityUtil.Fields(FIELD_LIST, null);
|
||||
|
||||
ResultList<Role> roles;
|
||||
if (before != null) { // Reverse paging
|
||||
roles = dao.listBefore(uriInfo, fields, null, limitParam, before); // Ask for one extra entry
|
||||
} else { // Forward paging or first page
|
||||
roles = dao.listAfter(uriInfo, fields, null, limitParam, after);
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}/versions")
|
||||
@Operation(
|
||||
summary = "List role versions",
|
||||
tags = "roles",
|
||||
description = "Get a list of all the versions of a role identified by `id`",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "List of role versions",
|
||||
content = @Content(mediaType = "application/json", schema = @Schema(implementation = EntityHistory.class)))
|
||||
})
|
||||
public EntityHistory listVersions(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Parameter(description = "role Id", schema = @Schema(type = "string")) @PathParam("id") String id)
|
||||
throws IOException, ParseException {
|
||||
return dao.listVersions(id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Valid
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Get a role",
|
||||
tags = "roles",
|
||||
description = "Get a role by `id`.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "The role",
|
||||
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Role.class))),
|
||||
@ApiResponse(responseCode = "404", description = "Role for instance {id} is not found")
|
||||
})
|
||||
public Role get(@Context UriInfo uriInfo, @Context SecurityContext securityContext, @PathParam("id") String id)
|
||||
throws IOException, ParseException {
|
||||
EntityUtil.Fields fields = new EntityUtil.Fields(FIELD_LIST, null);
|
||||
return dao.get(uriInfo, id, fields);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Valid
|
||||
@Path("/name/{name}")
|
||||
@Operation(
|
||||
summary = "Get a role by name",
|
||||
tags = "roles",
|
||||
description = "Get a role by `name`.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "The role",
|
||||
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Role.class))),
|
||||
@ApiResponse(responseCode = "404", description = "Role for instance {name} is not found")
|
||||
})
|
||||
public Role getByName(
|
||||
@Context UriInfo uriInfo, @Context SecurityContext securityContext, @PathParam("name") String name)
|
||||
throws IOException, ParseException {
|
||||
EntityUtil.Fields fields = new EntityUtil.Fields(FIELD_LIST, null);
|
||||
return dao.getByName(uriInfo, name, fields);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}/versions/{version}")
|
||||
@Operation(
|
||||
summary = "Get a version of the role",
|
||||
tags = "roles",
|
||||
description = "Get a version of the role by given `id`",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "role",
|
||||
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Role.class))),
|
||||
@ApiResponse(
|
||||
responseCode = "404",
|
||||
description = "Role for instance {id} and version {version} is " + "not found")
|
||||
})
|
||||
public Role getVersion(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Parameter(description = "Role Id", schema = @Schema(type = "string")) @PathParam("id") String id,
|
||||
@Parameter(
|
||||
description = "Role version number in the form `major`.`minor`",
|
||||
schema = @Schema(type = "string", example = "0.1 or 1.1"))
|
||||
@PathParam("version")
|
||||
String version)
|
||||
throws IOException, ParseException {
|
||||
return dao.getVersion(id, version);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Operation(
|
||||
summary = "Create a role",
|
||||
tags = "roles",
|
||||
description = "Create a new role.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "The role",
|
||||
content = @Content(mediaType = "application/json", schema = @Schema(implementation = CreateRole.class))),
|
||||
@ApiResponse(responseCode = "400", description = "Bad request")
|
||||
})
|
||||
public Response create(
|
||||
@Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreateRole createRole)
|
||||
throws IOException {
|
||||
SecurityUtil.checkAdminOrBotRole(authorizer, securityContext);
|
||||
Role role = getRole(createRole, securityContext);
|
||||
role = dao.create(uriInfo, role);
|
||||
return Response.created(role.getHref()).entity(role).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Operation(
|
||||
summary = "Create or Update a role",
|
||||
tags = "roles",
|
||||
description = "Create or Update a role.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "The role ",
|
||||
content = @Content(mediaType = "application/json", schema = @Schema(implementation = CreateRole.class))),
|
||||
@ApiResponse(responseCode = "400", description = "Bad request")
|
||||
})
|
||||
public Response createOrUpdateRole(
|
||||
@Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreateRole createRole)
|
||||
throws IOException, ParseException {
|
||||
SecurityUtil.checkAdminOrBotRole(authorizer, securityContext);
|
||||
Role role = getRole(createRole, securityContext);
|
||||
RestUtil.PutResponse<Role> response = dao.createOrUpdate(uriInfo, role);
|
||||
return response.toResponse();
|
||||
}
|
||||
|
||||
@PATCH
|
||||
@Path("/{id}")
|
||||
@Consumes(MediaType.APPLICATION_JSON_PATCH_JSON)
|
||||
@Operation(
|
||||
summary = "Update a role",
|
||||
tags = "roles",
|
||||
description = "Update an existing role with JsonPatch.",
|
||||
externalDocs = @ExternalDocumentation(description = "JsonPatch RFC", url = "https://tools.ietf.org/html/rfc6902"))
|
||||
public Response patch(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@PathParam("id") String id,
|
||||
@RequestBody(
|
||||
description = "JsonPatch with array of operations",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON_PATCH_JSON,
|
||||
examples = {
|
||||
@ExampleObject("[" + "{op:remove, path:/a}," + "{op:add, path: /b, value: val}" + "]")
|
||||
}))
|
||||
JsonPatch patch)
|
||||
throws IOException, ParseException {
|
||||
|
||||
SecurityUtil.checkAdminOrBotRole(authorizer, securityContext);
|
||||
PatchResponse<Role> response =
|
||||
dao.patch(uriInfo, UUID.fromString(id), securityContext.getUserPrincipal().getName(), patch);
|
||||
return response.toResponse();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Delete a role",
|
||||
tags = "roles",
|
||||
description = "Delete a role by given `id`.",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK"),
|
||||
@ApiResponse(responseCode = "404", description = "Role for instance {id} is not found")
|
||||
})
|
||||
public Response delete(@Context UriInfo uriInfo, @Context SecurityContext securityContext, @PathParam("id") String id)
|
||||
throws IOException {
|
||||
SecurityUtil.checkAdminOrBotRole(authorizer, securityContext);
|
||||
dao.delete(UUID.fromString(id));
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
private Role getRole(CreateRole ct, SecurityContext securityContext) {
|
||||
return new Role()
|
||||
.withId(UUID.randomUUID())
|
||||
.withName(ct.getName())
|
||||
.withDescription(ct.getDescription())
|
||||
.withDisplayName(ct.getDisplayName())
|
||||
.withUpdatedBy(securityContext.getUserPrincipal().getName())
|
||||
.withUpdatedAt(new Date());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
{
|
||||
"$id": "https://github.com/open-metadata/OpenMetadata/blob/main/catalog-rest-service/src/main/resources/json/schema/api/teams/createRole.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Create role entity request",
|
||||
"description": "Role entity",
|
||||
"type": "object",
|
||||
"properties" : {
|
||||
"name": {
|
||||
"$ref": "../../entity/teams/role.json#/definitions/roleName"
|
||||
},
|
||||
"displayName": {
|
||||
"description": "Optional name used for display purposes. Example 'Data Consumer'",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Optional description of the role",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
{
|
||||
"$id": "https://open-metadata.org/schema/entity/teams/role.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Role",
|
||||
"description": "This schema defines the Role entity. A Role has access to zero or more data assets",
|
||||
"type": "object",
|
||||
"definitions" : {
|
||||
"roleName" : {
|
||||
"description": "A unique name of the role.",
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
}
|
||||
},
|
||||
"properties" : {
|
||||
"id": {
|
||||
"$ref": "../../type/basic.json#/definitions/uuid"
|
||||
},
|
||||
"name": {
|
||||
"$ref": "#/definitions/roleName"
|
||||
},
|
||||
"displayName": {
|
||||
"description": "Name used for display purposes. Example 'Data Consumer'.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description of the role.",
|
||||
"type": "string"
|
||||
},
|
||||
"version" : {
|
||||
"description": "Metadata version of the entity.",
|
||||
"$ref": "../../type/entityHistory.json#/definitions/entityVersion"
|
||||
},
|
||||
"updatedAt" : {
|
||||
"description": "Last update time corresponding to the new version of the entity.",
|
||||
"$ref": "../../type/basic.json#/definitions/dateTime"
|
||||
},
|
||||
"updatedBy" : {
|
||||
"description": "User who made the update.",
|
||||
"type": "string"
|
||||
},
|
||||
"href": {
|
||||
"description": "Link to the resource corresponding to this entity.",
|
||||
"$ref": "../../type/basic.json#/definitions/href"
|
||||
},
|
||||
"changeDescription": {
|
||||
"description" : "Change that lead to this version of the entity.",
|
||||
"$ref": "../../type/entityHistory.json#/definitions/changeDescription"
|
||||
},
|
||||
"deleted" : {
|
||||
"description": "When `true` indicates the entity has been soft deleted.",
|
||||
"type": "boolean",
|
||||
"default" : false
|
||||
}
|
||||
},
|
||||
"required" : [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed 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.openmetadata.catalog.resources.teams;
|
||||
|
||||
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
||||
import static javax.ws.rs.core.Response.Status.FORBIDDEN;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.openmetadata.catalog.security.SecurityUtil.authHeaders;
|
||||
import static org.openmetadata.catalog.util.TestUtils.adminAuthHeaders;
|
||||
import static org.openmetadata.catalog.util.TestUtils.assertListNotNull;
|
||||
import static org.openmetadata.catalog.util.TestUtils.assertResponse;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import javax.json.JsonPatch;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import org.apache.http.client.HttpResponseException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.openmetadata.catalog.CatalogApplicationTest;
|
||||
import org.openmetadata.catalog.Entity;
|
||||
import org.openmetadata.catalog.api.teams.CreateRole;
|
||||
import org.openmetadata.catalog.entity.teams.Role;
|
||||
import org.openmetadata.catalog.exception.CatalogExceptionMessage;
|
||||
import org.openmetadata.catalog.jdbi3.RoleRepository.RoleEntityInterface;
|
||||
import org.openmetadata.catalog.resources.EntityResourceTest;
|
||||
import org.openmetadata.catalog.resources.teams.RoleResource.RoleList;
|
||||
import org.openmetadata.catalog.type.EntityReference;
|
||||
import org.openmetadata.catalog.util.EntityInterface;
|
||||
import org.openmetadata.catalog.util.JsonUtils;
|
||||
import org.openmetadata.catalog.util.TestUtils;
|
||||
import org.openmetadata.common.utils.JsonSchemaUtil;
|
||||
|
||||
public class RoleResourceTest extends EntityResourceTest<Role> {
|
||||
|
||||
public RoleResourceTest() {
|
||||
super(Entity.ROLE, Role.class, RoleList.class, "roles", null, false, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void post_validRoles_as_admin_200_OK(TestInfo test) throws IOException {
|
||||
// Create role with different optional fields
|
||||
CreateRole create = create(test, 1);
|
||||
createAndCheckEntity(create, adminAuthHeaders());
|
||||
|
||||
create = create(test, 2).withDisplayName("displayName");
|
||||
createAndCheckEntity(create, adminAuthHeaders());
|
||||
|
||||
create = create(test, 3).withDescription("description");
|
||||
createAndCheckEntity(create, adminAuthHeaders());
|
||||
|
||||
create = create(test, 4).withDisplayName("displayName").withDescription("description");
|
||||
createAndCheckEntity(create, adminAuthHeaders());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void post_validRoles_as_non_admin_401(TestInfo test) {
|
||||
// Create role with different optional fields
|
||||
Map<String, String> authHeaders = authHeaders("test@open-metadata.org");
|
||||
CreateRole create = create(test, 1);
|
||||
HttpResponseException exception =
|
||||
assertThrows(HttpResponseException.class, () -> createAndCheckEntity(create, authHeaders));
|
||||
assertResponse(exception, FORBIDDEN, "Principal: CatalogPrincipal{name='test'} is not admin");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityResourceTest#put_addDeleteFollower_200 for tests related getting role with entities owned by the role
|
||||
*/
|
||||
@Test
|
||||
public void delete_validRole_200_OK(TestInfo test) throws IOException {
|
||||
CreateRole create = create(test);
|
||||
Role role = createAndCheckEntity(create, adminAuthHeaders());
|
||||
deleteEntity(role.getId(), adminAuthHeaders());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete_validRole_as_non_admin_401(TestInfo test) throws IOException {
|
||||
CreateRole create = create(test);
|
||||
Role role = createAndCheckEntity(create, adminAuthHeaders());
|
||||
HttpResponseException exception =
|
||||
assertThrows(
|
||||
HttpResponseException.class, () -> deleteEntity(role.getId(), authHeaders("test@open-metadata.org")));
|
||||
assertResponse(exception, FORBIDDEN, "Principal: CatalogPrincipal{name='test'} is not admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patch_roleDeletedDisallowed_400(TestInfo test) throws HttpResponseException, JsonProcessingException {
|
||||
// Ensure role deleted attribute can't be changed using patch
|
||||
Role role = createRole(create(test), adminAuthHeaders());
|
||||
String roleJson = JsonUtils.pojoToJson(role);
|
||||
role.setDeleted(true);
|
||||
HttpResponseException exception =
|
||||
assertThrows(HttpResponseException.class, () -> patchRole(roleJson, role, adminAuthHeaders()));
|
||||
assertResponse(exception, BAD_REQUEST, CatalogExceptionMessage.readOnlyAttribute("Role", "deleted"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patch_roleAttributes_as_non_admin_403(TestInfo test)
|
||||
throws HttpResponseException, JsonProcessingException {
|
||||
// Create table without any attributes
|
||||
Role role = createRole(create(test), adminAuthHeaders());
|
||||
// Patching as a non-admin should is disallowed
|
||||
String originalJson = JsonUtils.pojoToJson(role);
|
||||
role.setDisplayName("newDisplayName");
|
||||
HttpResponseException exception =
|
||||
assertThrows(
|
||||
HttpResponseException.class,
|
||||
() -> patchRole(role.getId(), originalJson, role, authHeaders("test@open-metadata.org")));
|
||||
assertResponse(exception, FORBIDDEN, "Principal: CatalogPrincipal{name='test'} is not admin");
|
||||
}
|
||||
|
||||
public static Role createRole(CreateRole create, Map<String, String> authHeaders) throws HttpResponseException {
|
||||
return TestUtils.post(CatalogApplicationTest.getResource("roles"), create, Role.class, authHeaders);
|
||||
}
|
||||
|
||||
public static Role getRole(UUID id, String fields, Map<String, String> authHeaders) throws HttpResponseException {
|
||||
WebTarget target = CatalogApplicationTest.getResource("roles/" + id);
|
||||
target = fields != null ? target.queryParam("fields", fields) : target;
|
||||
return TestUtils.get(target, Role.class, authHeaders);
|
||||
}
|
||||
|
||||
public static Role getRoleByName(String name, String fields, Map<String, String> authHeaders)
|
||||
throws HttpResponseException {
|
||||
WebTarget target = CatalogApplicationTest.getResource("roles/name/" + name);
|
||||
target = fields != null ? target.queryParam("fields", fields) : target;
|
||||
return TestUtils.get(target, Role.class, authHeaders);
|
||||
}
|
||||
|
||||
private static void validateRole(
|
||||
Role role, String expectedDescription, String expectedDisplayName, String expectedUpdatedBy) {
|
||||
assertListNotNull(role.getId(), role.getHref());
|
||||
assertEquals(expectedDescription, role.getDescription());
|
||||
assertEquals(expectedUpdatedBy, role.getUpdatedBy());
|
||||
assertEquals(expectedDisplayName, role.getDisplayName());
|
||||
}
|
||||
|
||||
/** Validate returned fields GET .../roles/{id}?fields="..." or GET .../roles/name/{name}?fields="..." */
|
||||
@Override
|
||||
public void validateGetWithDifferentFields(Role expectedRole, boolean byName) throws HttpResponseException {
|
||||
String updatedBy = TestUtils.getPrincipal(adminAuthHeaders());
|
||||
// Role does not have any supported additional fields yet.
|
||||
// .../roles
|
||||
Role getRole =
|
||||
byName
|
||||
? getRoleByName(expectedRole.getName(), null, adminAuthHeaders())
|
||||
: getRole(expectedRole.getId(), null, adminAuthHeaders());
|
||||
validateRole(getRole, expectedRole.getDescription(), expectedRole.getDisplayName(), updatedBy);
|
||||
}
|
||||
|
||||
private Role patchRole(UUID roleId, String originalJson, Role updated, Map<String, String> authHeaders)
|
||||
throws JsonProcessingException, HttpResponseException {
|
||||
String updatedJson = JsonUtils.pojoToJson(updated);
|
||||
JsonPatch patch = JsonSchemaUtil.getJsonPatch(originalJson, updatedJson);
|
||||
return TestUtils.patch(CatalogApplicationTest.getResource("roles/" + roleId), patch, Role.class, authHeaders);
|
||||
}
|
||||
|
||||
private Role patchRole(String originalJson, Role updated, Map<String, String> authHeaders)
|
||||
throws JsonProcessingException, HttpResponseException {
|
||||
return patchRole(updated.getId(), originalJson, updated, authHeaders);
|
||||
}
|
||||
|
||||
CreateRole create(TestInfo test, int index) {
|
||||
return new CreateRole().withName(getEntityName(test) + index);
|
||||
}
|
||||
|
||||
public CreateRole create(TestInfo test) {
|
||||
return create(getEntityName(test));
|
||||
}
|
||||
|
||||
public CreateRole create(String entityName) {
|
||||
return new CreateRole().withName(entityName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createRequest(String name, String description, String displayName, EntityReference owner) {
|
||||
return create(name).withDescription(description).withDisplayName(displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateCreatedEntity(Role role, Object request, Map<String, String> authHeaders) {
|
||||
CreateRole createRequest = (CreateRole) request;
|
||||
validateCommonEntityFields(
|
||||
getEntityInterface(role), createRequest.getDescription(), TestUtils.getPrincipal(authHeaders), null);
|
||||
|
||||
assertEquals(createRequest.getDisplayName(), role.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUpdatedEntity(Role updatedEntity, Object request, Map<String, String> authHeaders) {
|
||||
validateCreatedEntity(updatedEntity, request, authHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compareEntities(Role expected, Role updated, Map<String, String> authHeaders) {
|
||||
validateCommonEntityFields(
|
||||
getEntityInterface(updated), expected.getDescription(), TestUtils.getPrincipal(authHeaders), null);
|
||||
|
||||
assertEquals(expected.getDisplayName(), updated.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityInterface<Role> getEntityInterface(Role entity) {
|
||||
return new RoleEntityInterface(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertFieldChange(String fieldName, Object expected, Object actual) throws IOException {
|
||||
assertCommonFieldChange(fieldName, expected, actual);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user