Add UpdateTeam permission support (#2994)

This commit is contained in:
Matt 2022-03-01 08:14:03 -08:00 committed by GitHub
parent c59a5e882d
commit 374eae4101
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 109 additions and 23 deletions

View File

@ -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;

View File

@ -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());

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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" }
]
}
},

View File

@ -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);
}
}));
}

View File

@ -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

View File

@ -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 {