Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement - Feature to add keycloak roles to the created user #144

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public class Constants {
public static final String USER_ID = "userId";
public static final String EMAIL = "email";
public static final String MOBILE = "mobile";
public static final String ROLES = "roles";
public static final String SVG_MEDIA_TYPE = "image/svg+xml";
public static final String CONNECTION_FAILURE = "CONNECTION_FAILURE";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public static Map<String, Object> convertJsonNodeToMap(JsonNode object) {
return mapObject;
}

public static List<String> convertJsonNodeToList(Object obj){
return new ObjectMapper().convertValue(obj, List.class);
}

public static String getStringWithReplacedText(String payload, String value, String replacement) {
Pattern pattern = Pattern.compile(value);
Matcher matcher = pattern.matcher(payload);
Expand Down
5 changes: 5 additions & 0 deletions java/middleware/registry-middleware/keycloak/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
<version>1.7.32</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.sunbirdrc</groupId>
<artifactId>middleware-commons</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dev.sunbirdrc.keycloak;

import com.fasterxml.jackson.databind.JsonNode;
import dev.sunbirdrc.registry.middleware.util.JSONUtil;
import dev.sunbirdrc.pojos.ComponentHealthInfo;
import dev.sunbirdrc.pojos.HealthIndicator;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
Expand All @@ -9,6 +11,7 @@
import org.keycloak.admin.client.resource.*;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -81,8 +84,9 @@ private Keycloak buildKeycloak(int httpMaxConnections) {
.build();
}

public String createUser(String entityName, String userName, String email, String mobile) throws OwnerCreationException {
public String createUser(String entityName, String userName, String email, String mobile, JsonNode realmRoles) throws OwnerCreationException {
logger.info("Creating user with mobile_number : " + userName);
List<String> roles = JSONUtil.convertJsonNodeToList(realmRoles);
UserRepresentation newUser = createUserRepresentation(entityName, userName, email, mobile);
GroupRepresentation entityGroup = createGroupRepresentation(entityName);
keycloak.realm(realm).groups().add(entityGroup);
Expand All @@ -93,12 +97,13 @@ public String createUser(String entityName, String userName, String email, Strin
logger.info("User ID path" + response.getLocation().getPath());
String userID = response.getLocation().getPath().replaceAll(".*/([^/]+)$", "$1");
logger.info("User ID : " + userID);
addRolesToUser(roles, userID);
if (!emailActions.isEmpty())
usersResource.get(userID).executeActionsEmail(emailActions);
return userID;
} else if (response.getStatus() == 409) {
logger.info("UserID: {} exists", userName);
return updateExistingUserAttributes(entityName, userName, email, mobile);
return updateExistingUserAttributes(entityName, userName, email, mobile, roles);
} else if (response.getStatus() == 500) {
throw new OwnerCreationException("Keycloak user creation error");
} else {
Expand All @@ -107,19 +112,38 @@ public String createUser(String entityName, String userName, String email, Strin
}
}

private void addRolesToUser(List<String> roles, String userID){
/** Add the 'view-realm' role to client to access the keycloak roles
* Go to Keycloak -> open client(which is configured as client_id in application.yml) ->
* Service Account Roles -> Client Roles, select 'realm-management' -> Assign 'view-relam' role
*/
if(!roles.isEmpty()) {
List<RoleRepresentation> roleToAdd = new ArrayList<>();
for (String role : roles) {
roleToAdd.add(keycloak.realm(realm).roles().get(role).toRepresentation());
}
UserResource userResource = keycloak.realm(realm).users().get(userID);
userResource.roles().realmLevel().add(roleToAdd);
logger.info("Added the roles: {}, to user: {}", roles, userID);
} else {
logger.info("No roles added to the user: {}", userID);
}
}

private GroupRepresentation createGroupRepresentation(String entityName) {
GroupRepresentation groupRepresentation = new GroupRepresentation();
groupRepresentation.setName(entityName);
return groupRepresentation;
}

private String updateExistingUserAttributes(String entityName, String userName, String email, String mobile) throws OwnerCreationException {
private String updateExistingUserAttributes(String entityName, String userName, String email, String mobile, List<String> roles) throws OwnerCreationException {
Optional<UserResource> userRepresentationOptional = getUserByUsername(userName);
if (userRepresentationOptional.isPresent()) {
UserResource userResource = userRepresentationOptional.get();
UserRepresentation userRepresentation = userResource.toRepresentation();
updateUserAttributes(entityName, email, mobile, userRepresentation);
userResource.update(userRepresentation);
addRolesToUser(roles, userName);
return userRepresentation.getId();
} else {
logger.error("Failed fetching user by username: {}", userName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ rule "Create entity owner for newly added owner fields"
stateDefinition:StateContext(isOwnershipProperty() && isOwnerNewlyAdded() && isLoginEnabled());
then
String owner = keycloakAdminUtil.createUser(stateDefinition.getEntityName(), stateDefinition.getUpdated().get("userId").textValue(),
stateDefinition.getUpdated().get("email").textValue(), stateDefinition.getUpdated().get("mobile").textValue());
stateDefinition.getUpdated().get("email").textValue(), stateDefinition.getUpdated().get("mobile").textValue(), stateDefinition.getUpdated().get("roles"));
stateDefinition.addOwner(owner);
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ private ObjectNode createOwnershipNode(JsonNode entityNode, String entityName, O
objectNode.put(MOBILE, entityNode.at(String.format("/%s%s", entityName, mobilePath)).asText(""));
objectNode.put(EMAIL, entityNode.at(String.format("/%s%s", entityName, emailPath)).asText(""));
objectNode.put(USER_ID, entityNode.at(String.format("/%s%s", entityName, userIdPath)).asText(""));
objectNode.set(ROLES, entityNode.at(String.format("/%s", entityName)).has(ROLES) ? entityNode.at(String.format("/%s%s", entityName, "/roles")) : new ObjectMapper().createArrayNode());
return objectNode;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -111,21 +112,21 @@ public void shouldBeNoStateChangeIfTheDataDidNotChange() throws IOException {

@Test
public void shouldCreateNewOwnersForNewlyAddedOwnerFields() throws IOException, DuplicateRecordException, EntityCreationException, OwnerCreationException {
when(keycloakAdminUtil.createUser(anyString(), anyString(), anyString(), anyString())).thenReturn("456");
when(keycloakAdminUtil.createUser(anyString(), anyString(), anyString(), anyString(), any())).thenReturn("456");
JsonNode test = m.readTree(new File(getBaseDir() + "shouldAddNewOwner.json"));
runTest(test.get("existing"), test.get("updated"), test.get("expected"), Collections.emptyList());
}

@Test
public void shouldNotCreateNewOwners() throws IOException, DuplicateRecordException, EntityCreationException, OwnerCreationException {
when(keycloakAdminUtil.createUser(anyString(), anyString(), anyString(), anyString())).thenReturn("456");
when(keycloakAdminUtil.createUser(anyString(), anyString(), anyString(), anyString(), any())).thenReturn("456");
JsonNode test = m.readTree(new File(getBaseDir() + "shouldNotAddNewOwner.json"));
runTest(test.get("existing"), test.get("updated"), test.get("expected"), Collections.emptyList());
}

@Test
public void shouldNotModifyExistingOwners() throws IOException, DuplicateRecordException, EntityCreationException, OwnerCreationException {
when(keycloakAdminUtil.createUser(anyString(), anyString(), anyString(), anyString())).thenReturn("456");
when(keycloakAdminUtil.createUser(anyString(), anyString(), anyString(), anyString(),any())).thenReturn("456");
JsonNode test = m.readTree(new File(getBaseDir() + "shouldNotModifyExistingOwner.json"));
runTest(test.get("existing"), test.get("updated"), test.get("expected"), Collections.emptyList());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ public void shouldCreateOwnersForInvite() throws Exception {
JsonNode inviteJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"[email protected]\",\"instituteName\":\"gecasu\"}}");
mockDefinitionManager();
String testUserId = "be6d30e9-7c62-4a05-b4c8-ee28364da8e4";
when(keycloakAdminUtil.createUser(any(), any(), any(), any())).thenReturn(testUserId);
when(keycloakAdminUtil.createUser(any(), any(), any(), any(), any())).thenReturn(testUserId);
when(registryService.addEntity(any(), any(), any(), anyBoolean())).thenReturn(UUID.randomUUID().toString());
when(shardManager.getShard(any())).thenReturn(new Shard());
ReflectionTestUtils.setField(registryHelper, "workflowEnabled", true);
Expand All @@ -355,7 +355,7 @@ public void shouldSendInviteInvitationsAfterCreatingOwners() throws Exception {
JsonNode inviteJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"[email protected]\",\"instituteName\":\"gecasu\"}}");
mockDefinitionManager();
String testUserId = "be6d30e9-7c62-4a05-b4c8-ee28364da8e4";
when(keycloakAdminUtil.createUser(any(), any(), any(), any())).thenReturn(testUserId);
when(keycloakAdminUtil.createUser(any(), any(), any(), any(), any())).thenReturn(testUserId);
when(registryService.addEntity(any(), any(), any(), anyBoolean())).thenReturn(UUID.randomUUID().toString());
when(shardManager.getShard(any())).thenReturn(new Shard());
ReflectionTestUtils.setField(registryHelper, "notificationEnabled", true);
Expand Down Expand Up @@ -386,7 +386,7 @@ public void shouldSendMultipleInviteInvitationsAfterCreatingOwners() throws Exce
" \"adminMobile\": \"1234\"\n" +
"}}");
String testUserId = "be6d30e9-7c62-4a05-b4c8-ee28364da8e4";
when(keycloakAdminUtil.createUser(any(), any(), any(), any())).thenReturn(testUserId);
when(keycloakAdminUtil.createUser(any(), any(), any(), any(), any())).thenReturn(testUserId);
when(registryService.addEntity(any(), any(), any(), anyBoolean())).thenReturn(UUID.randomUUID().toString());
when(shardManager.getShard(any())).thenReturn(new Shard());
mockDefinitionManager();
Expand Down