mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-06 04:26:57 +00:00
Add UpdateTeam permission support (#2994)
This commit is contained in:
parent
c59a5e882d
commit
374eae4101
@ -179,6 +179,11 @@ public final class Entity {
|
||||
return ref.withHref(href);
|
||||
}
|
||||
|
||||
public static boolean shouldHaveOwner(@NonNull String entityType) {
|
||||
// Team does not have an owner. (yet?)
|
||||
return !entityType.equals(TEAM);
|
||||
}
|
||||
|
||||
public static <T> EntityInterface<T> getEntityInterface(T entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
|
||||
@ -336,8 +336,10 @@ public class TeamResource {
|
||||
}))
|
||||
JsonPatch patch)
|
||||
throws IOException, ParseException {
|
||||
|
||||
SecurityUtil.checkAdminOrBotRole(authorizer, securityContext);
|
||||
Fields fields = new Fields(ALLOWED_FIELDS, FIELDS);
|
||||
Team team = dao.get(uriInfo, id, fields);
|
||||
SecurityUtil.checkAdminRoleOrPermissions(
|
||||
authorizer, securityContext, dao.getEntityInterface(team).getEntityReference(), patch);
|
||||
PatchResponse<Team> response =
|
||||
dao.patch(uriInfo, UUID.fromString(id), securityContext.getUserPrincipal().getName(), patch);
|
||||
addHref(uriInfo, response.getEntity());
|
||||
|
||||
@ -145,11 +145,11 @@ public class DefaultAuthorizer implements Authorizer {
|
||||
|
||||
Object entity = Entity.getEntity(entityReference, new EntityUtil.Fields(List.of("tags", FIELD_OWNER)));
|
||||
EntityReference owner = Entity.getEntityInterface(entity).getOwner();
|
||||
if (owner == null) {
|
||||
if (Entity.shouldHaveOwner(entityReference.getType()) && owner == null) {
|
||||
// Entity does not have an owner.
|
||||
return true;
|
||||
}
|
||||
if (isOwnedByUser(user, owner)) {
|
||||
if (Entity.shouldHaveOwner(entityReference.getType()) && isOwnedByUser(user, owner)) {
|
||||
// Entity is owned by the user.
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -87,9 +87,9 @@ public final class SecurityUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Most REST API requests should yield in a single metadata operation. There are cases where the JSON patch request
|
||||
* may yield multiple metadata operations. This helper function checks if user has permission to perform the given set
|
||||
* of metadata operations.
|
||||
* Most REST API requests should yield a single metadata operation. There are cases where the JSON patch request may
|
||||
* yield multiple metadata operations. This helper function checks if user has permission to perform the given set of
|
||||
* metadata operations that can be derived from JSON patch.
|
||||
*/
|
||||
public static void checkAdminRoleOrPermissions(
|
||||
Authorizer authorizer, SecurityContext securityContext, EntityReference entityReference, JsonPatch patch) {
|
||||
@ -101,6 +101,11 @@ public final class SecurityUtil {
|
||||
}
|
||||
|
||||
List<MetadataOperation> metadataOperations = JsonPatchUtils.getMetadataOperations(patch);
|
||||
|
||||
// If there are no specific metadata operations that can be determined from the JSON Patch, deny the changes.
|
||||
if (metadataOperations.isEmpty()) {
|
||||
throw new AuthorizationException(noPermission(principal));
|
||||
}
|
||||
for (MetadataOperation metadataOperation : metadataOperations) {
|
||||
if (!authorizer.hasPermissions(authenticationCtx, entityReference, metadataOperation)) {
|
||||
throw new AuthorizationException(noPermission(principal, metadataOperation.value()));
|
||||
@ -150,14 +155,4 @@ public final class SecurityUtil {
|
||||
}
|
||||
return target.request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if authentication is enabled.
|
||||
*
|
||||
* @param securityContext security context
|
||||
* @return true if jwt filter based authentication is enabled, false otherwise
|
||||
*/
|
||||
public static boolean isSecurityEnabled(SecurityContext securityContext) {
|
||||
return !securityContext.getAuthenticationScheme().equals(SecurityContext.BASIC_AUTH);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,9 @@ public class JsonPatchUtils {
|
||||
if (path.contains(FIELD_OWNER)) {
|
||||
return MetadataOperation.UpdateOwner;
|
||||
}
|
||||
if (path.startsWith("/users")) { // Ability to update users within a team.
|
||||
return MetadataOperation.UpdateTeam;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,8 @@
|
||||
"UpdateOwner",
|
||||
"UpdateTags",
|
||||
"UpdateLineage",
|
||||
"DecryptTokens"
|
||||
"DecryptTokens",
|
||||
"UpdateTeam"
|
||||
],
|
||||
"javaEnums": [
|
||||
{ "name": "SuggestDescription" },
|
||||
@ -26,7 +27,8 @@
|
||||
{ "name": "UpdateOwner" },
|
||||
{ "name": "UpdateTags" },
|
||||
{ "name": "UpdateLineage" },
|
||||
{ "name": "DecryptTokens" }
|
||||
{ "name": "DecryptTokens" },
|
||||
{ "name": "UpdateTeam" }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@ -92,6 +92,7 @@ class PermisssionsResourceTest extends CatalogApplicationTest {
|
||||
put(MetadataOperation.UpdateOwner, Boolean.TRUE);
|
||||
put(MetadataOperation.UpdateTags, Boolean.TRUE);
|
||||
put(MetadataOperation.DecryptTokens, Boolean.TRUE);
|
||||
put(MetadataOperation.UpdateTeam, Boolean.TRUE);
|
||||
}
|
||||
}),
|
||||
Arguments.of(
|
||||
@ -105,6 +106,7 @@ class PermisssionsResourceTest extends CatalogApplicationTest {
|
||||
put(MetadataOperation.UpdateOwner, Boolean.TRUE);
|
||||
put(MetadataOperation.UpdateTags, Boolean.TRUE);
|
||||
put(MetadataOperation.DecryptTokens, Boolean.FALSE);
|
||||
put(MetadataOperation.UpdateTeam, Boolean.FALSE);
|
||||
}
|
||||
}),
|
||||
Arguments.of(
|
||||
@ -118,6 +120,7 @@ class PermisssionsResourceTest extends CatalogApplicationTest {
|
||||
put(MetadataOperation.UpdateOwner, Boolean.FALSE);
|
||||
put(MetadataOperation.UpdateTags, Boolean.FALSE);
|
||||
put(MetadataOperation.DecryptTokens, Boolean.FALSE);
|
||||
put(MetadataOperation.UpdateTeam, Boolean.FALSE);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@ -19,10 +19,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.openmetadata.catalog.exception.CatalogExceptionMessage.notAdmin;
|
||||
import static org.openmetadata.catalog.util.TestUtils.ADMIN_AUTH_HEADERS;
|
||||
import static org.openmetadata.catalog.util.TestUtils.TEST_AUTH_HEADERS;
|
||||
import static org.openmetadata.catalog.util.TestUtils.TEST_USER_NAME;
|
||||
import static org.openmetadata.catalog.util.TestUtils.UpdateType.MINOR_UPDATE;
|
||||
import static org.openmetadata.catalog.util.TestUtils.assertListNotNull;
|
||||
import static org.openmetadata.catalog.util.TestUtils.assertResponse;
|
||||
import static org.openmetadata.catalog.util.TestUtils.validateEntityReferences;
|
||||
@ -45,18 +45,25 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.openmetadata.catalog.Entity;
|
||||
import org.openmetadata.catalog.api.teams.CreateTeam;
|
||||
import org.openmetadata.catalog.entity.policies.Policy;
|
||||
import org.openmetadata.catalog.entity.policies.accessControl.Rule;
|
||||
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.exception.CatalogExceptionMessage;
|
||||
import org.openmetadata.catalog.jdbi3.TeamRepository.TeamEntityInterface;
|
||||
import org.openmetadata.catalog.jdbi3.UserRepository;
|
||||
import org.openmetadata.catalog.resources.EntityResourceTest;
|
||||
import org.openmetadata.catalog.resources.locations.LocationResourceTest;
|
||||
import org.openmetadata.catalog.resources.policies.PolicyResource;
|
||||
import org.openmetadata.catalog.resources.policies.PolicyResourceTest;
|
||||
import org.openmetadata.catalog.resources.teams.TeamResource.TeamList;
|
||||
import org.openmetadata.catalog.security.SecurityUtil;
|
||||
import org.openmetadata.catalog.type.ChangeDescription;
|
||||
import org.openmetadata.catalog.type.EntityReference;
|
||||
import org.openmetadata.catalog.type.FieldChange;
|
||||
import org.openmetadata.catalog.type.ImageList;
|
||||
import org.openmetadata.catalog.type.MetadataOperation;
|
||||
import org.openmetadata.catalog.type.Profile;
|
||||
import org.openmetadata.catalog.util.EntityInterface;
|
||||
import org.openmetadata.catalog.util.JsonUtils;
|
||||
@ -175,7 +182,76 @@ public class TeamResourceTest extends EntityResourceTest<Team, CreateTeam> {
|
||||
String originalJson = JsonUtils.pojoToJson(team);
|
||||
team.setDisplayName("newDisplayName");
|
||||
assertResponse(
|
||||
() -> patchEntity(team.getId(), originalJson, team, TEST_AUTH_HEADERS), FORBIDDEN, notAdmin(TEST_USER_NAME));
|
||||
() -> patchEntity(team.getId(), originalJson, team, TEST_AUTH_HEADERS),
|
||||
FORBIDDEN,
|
||||
CatalogExceptionMessage.noPermission(TEST_USER_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
void patch_teamUsers_as_user_with_UpdateTeam_permission(TestInfo test) throws IOException {
|
||||
UserResourceTest userResourceTest = new UserResourceTest();
|
||||
List<EntityReference> userRefs = new ArrayList<>();
|
||||
for (int i = 0; i < 7; i++) {
|
||||
User user = userResourceTest.createEntity(userResourceTest.createRequest(test, i), ADMIN_AUTH_HEADERS);
|
||||
userRefs.add(new UserRepository.UserEntityInterface(user).getEntityReference());
|
||||
}
|
||||
|
||||
Team team = createEntity(createRequest(test), ADMIN_AUTH_HEADERS);
|
||||
String originalJson = JsonUtils.pojoToJson(team);
|
||||
team.setUsers(userRefs);
|
||||
|
||||
// Ensure user without UpdateTeam permission cannot add users to a team.
|
||||
String randomUserName = userRefs.get(0).getName();
|
||||
assertResponse(
|
||||
() ->
|
||||
patchEntity(
|
||||
team.getId(), originalJson, team, SecurityUtil.authHeaders(randomUserName + "@open-metadata.org")),
|
||||
FORBIDDEN,
|
||||
CatalogExceptionMessage.noPermission(randomUserName, "UpdateTeam"));
|
||||
|
||||
// Ensure user with UpdateTeam permission can add users to a team.
|
||||
User teamManagerUser = createTeamManager(test);
|
||||
FieldChange fieldChange = new FieldChange().withName("users").withNewValue(userRefs);
|
||||
ChangeDescription change =
|
||||
getChangeDescription(team.getVersion()).withFieldsAdded(Collections.singletonList(fieldChange));
|
||||
patchEntityAndCheck(
|
||||
team,
|
||||
originalJson,
|
||||
SecurityUtil.authHeaders(teamManagerUser.getName() + "@open-metadata.org"),
|
||||
MINOR_UPDATE,
|
||||
change);
|
||||
}
|
||||
|
||||
private User createTeamManager(TestInfo testInfo) throws HttpResponseException, JsonProcessingException {
|
||||
// Create TeamManager role.
|
||||
RoleResourceTest roleResourceTest = new RoleResourceTest();
|
||||
Role teamManager =
|
||||
roleResourceTest.createEntity(
|
||||
roleResourceTest.createRequest(testInfo).withName("TeamManager"), ADMIN_AUTH_HEADERS);
|
||||
|
||||
// Ensure TeamManager has permission to UpdateTeam.
|
||||
PolicyResourceTest policyResourceTest = new PolicyResourceTest();
|
||||
Policy policy =
|
||||
policyResourceTest.getEntityByName(
|
||||
"TeamManagerRoleAccessControlPolicy", PolicyResource.FIELDS, ADMIN_AUTH_HEADERS);
|
||||
String originalJson = JsonUtils.pojoToJson(policy);
|
||||
Rule rule =
|
||||
new Rule()
|
||||
.withName("TeamManagerRoleAccessControlPolicy-UpdateTeam")
|
||||
.withAllow(true)
|
||||
.withUserRoleAttr("TeamManager")
|
||||
.withOperation(MetadataOperation.UpdateTeam);
|
||||
policy.setRules(List.of(rule));
|
||||
policyResourceTest.patchEntity(policy.getId(), originalJson, policy, ADMIN_AUTH_HEADERS);
|
||||
|
||||
// Create a user with TeamManager role.
|
||||
UserResourceTest userResourceTest = new UserResourceTest();
|
||||
return userResourceTest.createEntity(
|
||||
userResourceTest
|
||||
.createRequest(testInfo)
|
||||
.withName(getEntityName(testInfo) + "manager")
|
||||
.withRoles(List.of(teamManager.getId())),
|
||||
ADMIN_AUTH_HEADERS);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -29,9 +29,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openmetadata.catalog.entity.teams.Team;
|
||||
|
||||
@Slf4j
|
||||
/** This test provides examples of how to use applyPatch */
|
||||
class JsonUtilsTest {
|
||||
@Slf4j
|
||||
public class JsonUtilsTest {
|
||||
/** Test apply patch method with different operations. */
|
||||
@Test
|
||||
void applyPatch() throws IOException {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user