Skip to content

Commit

Permalink
Assign default roles from Teams to User (#2947)
Browse files Browse the repository at this point in the history
  • Loading branch information
mithmatt committed Feb 24, 2022
1 parent 9dd8598 commit 8e99867
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -373,8 +373,11 @@ private void setDefaultToFalse(Role role) {
private List<User> getAllUsers() {
EntityRepository<User> userRepository = Entity.getEntityRepository(Entity.USER);
try {
// Assumptions:
// - we will not have more than Integer.MAX_VALUE users in the system.
// - we do not need to update deleted user's roles.
return userRepository
.listAfter(null, UserRepository.USER_UPDATE_FIELDS, null, Integer.MAX_VALUE - 1, null, Include.ALL)
.listAfter(null, UserRepository.USER_UPDATE_FIELDS, null, Integer.MAX_VALUE - 1, null, Include.NON_DELETED)
.getData();
} catch (GeneralSecurityException | IOException | ParseException e) {
throw EntityNotFoundException.byMessage(CatalogExceptionMessage.entitiesNotFound(Entity.USER));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand All @@ -31,6 +30,7 @@
import lombok.extern.slf4j.Slf4j;
import org.jdbi.v3.sqlobject.transaction.Transaction;
import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.entity.teams.Team;
import org.openmetadata.catalog.entity.teams.User;
import org.openmetadata.catalog.resources.teams.UserResource;
import org.openmetadata.catalog.type.ChangeDescription;
Expand Down Expand Up @@ -66,27 +66,38 @@ public EntityInterface<User> getEntityInterface(User entity) {

/** Ensures that the default roles are added for POST, PUT and PATCH operations. */
@Override
public void prepare(User user) throws IOException {
List<EntityReference> rolesRef = user.getRoles();
Set<UUID> existingRoleIds = new HashSet<>();
if (rolesRef != null) {
existingRoleIds = user.getRoles().stream().map(EntityReference::getId).collect(Collectors.toSet());
}

// Find default roles to add.
Set<UUID> defaultRoleIds =
daoCollection.roleDAO().getDefaultRolesIds().stream().map(UUID::fromString).collect(Collectors.toSet());
defaultRoleIds.removeAll(existingRoleIds);

if (rolesRef == null || rolesRef.isEmpty()) {
rolesRef = new ArrayList<>();
}
for (UUID roleId : defaultRoleIds) {
public void prepare(User user) throws IOException, ParseException {
// Get roles assigned to the user.
Set<UUID> roleIds =
Optional.ofNullable(user.getRoles()).orElse(Collections.emptyList()).stream()
.map(EntityReference::getId)
.collect(Collectors.toSet());
// Get default role set up globally.
daoCollection.roleDAO().getDefaultRolesIds().forEach(roleIdStr -> roleIds.add(UUID.fromString(roleIdStr)));
// Get default roles from the teams that the user belongs to.
getTeamDefaultRoles(user).forEach(roleRef -> roleIds.add(roleRef.getId()));

// Assign roles.
List<EntityReference> rolesRef = new ArrayList<>(roleIds.size());
for (UUID roleId : roleIds) {
rolesRef.add(daoCollection.roleDAO().findEntityReferenceById(roleId));
}
rolesRef.sort(EntityUtil.compareEntityReference);
user.setRoles(rolesRef);
}

private List<EntityReference> getTeamDefaultRoles(User user) throws IOException, ParseException {
List<EntityReference> teamsRef = Optional.ofNullable(user.getTeams()).orElse(Collections.emptyList());
List<EntityReference> defaultRoles = new ArrayList<>();
for (EntityReference teamRef : teamsRef) {
Team team = Entity.getEntity(teamRef, new Fields(List.of("defaultRoles")));
if (team != null && team.getDefaultRoles() != null) {
defaultRoles.addAll(team.getDefaultRoles());
}
}
return defaultRoles;
}

@Override
public void storeEntity(User user, boolean update) throws IOException {
// Relationships and fields such as href are derived and not stored as part of json
Expand Down Expand Up @@ -351,7 +362,7 @@ public void entitySpecificUpdate() throws IOException {
recordChange("email", original.getEntity().getEmail(), updated.getEntity().getEmail());
}

private void updateRoles(User origUser, User updatedUser) throws JsonProcessingException {
private void updateRoles(User origUser, User updatedUser) throws IOException {
// Remove roles from original and add roles from updated
daoCollection
.relationshipDAO()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import javax.validation.constraints.Positive;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpResponseException;
Expand Down Expand Up @@ -78,25 +79,19 @@ void defaultRole(TestInfo test) throws IOException {

List<User> users = new UserResourceTest().listEntities(Map.of("fields", "roles"), ADMIN_AUTH_HEADERS).getData();
for (User user : users) {
boolean defaultRoleSet = false, prevDefaultRoleExists = false;

for (EntityReference role : user.getRoles()) {
if (role.getId().equals(defaultRole.getId())) {
defaultRoleSet = true;
}
if (role.getId().equals(prevDefaultRole.getId())) {
prevDefaultRoleExists = true;
}
}
if (!defaultRoleSet) {
fail(String.format("Default role %s was not set for user %s", defaultRole.getName(), user.getName()));
}
UUID prevDefaultRoleId = prevDefaultRole.getId();
boolean prevDefaultRoleExists =
user.getRoles().stream().anyMatch(role -> role.getId().equals(prevDefaultRoleId));
if (prevDefaultRoleExists) {
fail(
String.format(
"Previous default role %s has not been removed for user %s",
prevDefaultRole.getName(), user.getName()));
}
boolean defaultRoleExists = user.getRoles().stream().anyMatch(role -> role.getId().equals(defaultRole.getId()));
if (!defaultRoleExists) {
fail(String.format("Default role %s was not set for user %s", defaultRole.getName(), user.getName()));
}
}
prevDefaultRole = defaultRole;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -100,27 +101,90 @@ public void post_entity_as_non_admin_401(TestInfo test) {

@Order(Integer.MAX_VALUE) // Run this test last to avoid side effects of default role creation to fail other tests.
@Test
void post_userWithDefaultRole(TestInfo test) throws IOException {
void userDefaultRoleAssignment(TestInfo test) throws IOException {
// Given a global default role has been set, ...
RoleResourceTest roleResourceTest = new RoleResourceTest();
List<Role> roles = roleResourceTest.listEntities(Collections.emptyMap(), ADMIN_AUTH_HEADERS).getData();
UUID nonDefaultRoleId = roles.stream().filter(role -> !role.getDefault()).findAny().orElseThrow().getId();
UUID defaultRoleId =
roles.stream().filter(Role::getDefault).findAny().orElseThrow().getId(); // DataConsumer is default role.
roles.stream().filter(Role::getDefault).findAny().orElseThrow().getId(); // DataConsumer is global default role.

// ... when a user is created without any roles, then the global default role should be assigned.
CreateUser create = createRequest(test, 1);
User user1 = createUserAndCheckRoles(create, Arrays.asList(defaultRoleId));

// ... when a user is created with a non default role, then the global default role should be assigned along with
// the non default role.
create = createRequest(test, 2).withRoles(List.of(nonDefaultRoleId));
User user2 = createUserAndCheckRoles(create, Arrays.asList(nonDefaultRoleId, defaultRoleId));

// ... when a user is created with both global default and non-default role, then both roles should be assigned.
create = createRequest(test, 3).withRoles(List.of(nonDefaultRoleId, defaultRoleId));
User user3 = createUserAndCheckRoles(create, Arrays.asList(nonDefaultRoleId, defaultRoleId));

// Given the default role has changed, ...
UUID prevDefaultRoleId = defaultRoleId;
Role defaultRole = roleResourceTest.createEntity(roleResourceTest.createRequest(test, 1000), ADMIN_AUTH_HEADERS);
String defaultRoleJson = JsonUtils.pojoToJson(defaultRole);
defaultRole.setDefault(true);
defaultRoleId = defaultRole.getId(); // New global default role.
assertNotEquals(prevDefaultRoleId, defaultRoleId);
roleResourceTest.patchEntity(defaultRoleId, defaultRoleJson, defaultRole, ADMIN_AUTH_HEADERS);

// Given a default role has been set, when a user is created without any roles, then the default role should be
// assigned.
CreateUser create = createRequest(test, 2);
createUserAndCheckRoles(create, Arrays.asList(defaultRoleId));
// ... when user1 exists, then ensure that the default role changed.
user1 = getEntity(user1.getId(), ADMIN_AUTH_HEADERS);
assertRoles(user1, Arrays.asList(defaultRoleId));

// Given a default role has been set, when a user is created with a non default role, then the default role should
// be assigned along with the non default role.
create = createRequest(test, 3).withRoles(List.of(nonDefaultRoleId));
createUserAndCheckRoles(create, Arrays.asList(nonDefaultRoleId, defaultRoleId));
// ... when user2 exists, then ensure that the default role changed.
user2 = getEntity(user2.getId(), ADMIN_AUTH_HEADERS);
assertRoles(user2, Arrays.asList(nonDefaultRoleId, defaultRoleId));

// Given a default role has been set, when a user is created with both default and non-default role, then both
// roles should be assigned.
create = createRequest(test, 4).withRoles(List.of(nonDefaultRoleId, defaultRoleId));
createUserAndCheckRoles(create, Arrays.asList(nonDefaultRoleId, defaultRoleId));
// ... when user3 exists, then ensure that the default role changed.
user3 = getEntity(user3.getId(), ADMIN_AUTH_HEADERS);
assertRoles(user3, Arrays.asList(nonDefaultRoleId, defaultRoleId));

Role role1 = roleResourceTest.createEntity(roleResourceTest.createRequest(test, 1001), ADMIN_AUTH_HEADERS);
Role role2 = roleResourceTest.createEntity(roleResourceTest.createRequest(test, 1002), ADMIN_AUTH_HEADERS);

TeamResourceTest teamResourceTest = new TeamResourceTest();
Team team1 =
teamResourceTest.createEntity(
teamResourceTest.createRequest(test, 1).withDefaultRoles(List.of(role1.getId())), ADMIN_AUTH_HEADERS);
Team team2 =
teamResourceTest.createEntity(
teamResourceTest.createRequest(test, 2).withDefaultRoles(List.of(role1.getId(), role2.getId())),
ADMIN_AUTH_HEADERS);

// Given user1 is not part of any team and have no roles assigned, when user1 joins team1, then user1 gets assigned
// the global default role and the team1 default role, role1.
String originalUser1 = JsonUtils.pojoToJson(user1);
user1.setTeams(List.of(new TeamEntityInterface(team1).getEntityReference()));
user1 = patchUser(originalUser1, user1, ADMIN_AUTH_HEADERS);
assertRoles(user1, Arrays.asList(defaultRoleId, role1.getId()));

// Given user1 is part of team1, when user1 joins team2, then user1 gets assigned the global default role, the
// team1 default role, role1 and team2 default role, role2.
originalUser1 = JsonUtils.pojoToJson(user1);
user1.setTeams(
List.of(
new TeamEntityInterface(team1).getEntityReference(), new TeamEntityInterface(team2).getEntityReference()));
user1 = patchUser(originalUser1, user1, ADMIN_AUTH_HEADERS);
assertRoles(user1, Arrays.asList(defaultRoleId, role1.getId(), role2.getId()));

// Given user2 has a non default role assigned, when user2 joins team2, then user2 should get assigned the global
// default role, team2 default roles, role1 and role2, and retain its non-default role.
String originalUser2 = JsonUtils.pojoToJson(user2);
user2.setTeams(List.of(new TeamEntityInterface(team2).getEntityReference()));
user2 = patchUser(originalUser2, user2, ADMIN_AUTH_HEADERS);
assertRoles(user2, Arrays.asList(defaultRoleId, role1.getId(), role2.getId(), nonDefaultRoleId));

// Given user2 has a non default role assigned, when user2 leaves team2, then user2 should get assigned the global
// default role and retain its non-default role.
// To be fixed in https://github.com/open-metadata/OpenMetadata/issues/2969
// originalUser2 = JsonUtils.pojoToJson(user2);
// user2.setTeams(null);
// user2 = patchUser(originalUser2, user2, ADMIN_AUTH_HEADERS);
// assertRoles(user2, Arrays.asList(defaultRoleId, nonDefaultRoleId));
}

@Test
Expand Down Expand Up @@ -551,8 +615,13 @@ void delete_validUser_as_admin_200(TestInfo test) throws IOException {
// TODO deactivated user can't be made owner
}

private void createUserAndCheckRoles(CreateUser create, List<UUID> expectedRolesIds) throws HttpResponseException {
private User createUserAndCheckRoles(CreateUser create, List<UUID> expectedRolesIds) throws HttpResponseException {
User user = createEntity(create, ADMIN_AUTH_HEADERS);
assertRoles(user, expectedRolesIds);
return user;
}

private void assertRoles(User user, List<UUID> expectedRolesIds) throws HttpResponseException {
user = getEntity(user.getId(), ADMIN_AUTH_HEADERS);
List<UUID> actualRolesIds =
user.getRoles().stream().map(EntityReference::getId).sorted().collect(Collectors.toList());
Expand Down

0 comments on commit 8e99867

Please sign in to comment.