From 1439771487400c35abf9e61a697c05828adaf796 Mon Sep 17 00:00:00 2001 From: Shiva Rakshith Date: Fri, 10 Jun 2022 15:18:45 +0530 Subject: [PATCH 1/5] feat: Implementation of adding keycloak roles --- .../registry/middleware/util/Constants.java | 1 + .../registry/middleware/util/JSONUtil.java | 4 +++ .../registry-middleware/keycloak/pom.xml | 5 ++++ .../sunbirdrc/keycloak/KeycloakAdminUtil.java | 27 ++++++++++++++++--- .../resources/workflow/statetransitions.drl | 2 +- .../registry/helper/EntityStateHelper.java | 1 + .../helper/EntityStateHelperTest.java | 7 ++--- .../registry/helper/RegistryHelperTest.java | 6 ++--- 8 files changed, 43 insertions(+), 10 deletions(-) diff --git a/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/Constants.java b/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/Constants.java index 0de0f07bd..8224468c4 100644 --- a/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/Constants.java +++ b/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/Constants.java @@ -110,6 +110,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 enum GraphDatabaseProvider { diff --git a/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/JSONUtil.java b/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/JSONUtil.java index b5eb22894..4e4b9fec6 100644 --- a/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/JSONUtil.java +++ b/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/JSONUtil.java @@ -69,6 +69,10 @@ public static Map convertJsonNodeToMap(JsonNode object) { return mapObject; } + public static List 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); diff --git a/java/middleware/registry-middleware/keycloak/pom.xml b/java/middleware/registry-middleware/keycloak/pom.xml index 821ffb43d..779b31106 100644 --- a/java/middleware/registry-middleware/keycloak/pom.xml +++ b/java/middleware/registry-middleware/keycloak/pom.xml @@ -44,6 +44,11 @@ 1.7.32 compile + + dev.sunbirdrc + middleware-commons + 2.0.3 + org.springframework.boot spring-boot-starter-test diff --git a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java index 62c6aca03..88c1de978 100644 --- a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java +++ b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java @@ -1,11 +1,14 @@ package dev.sunbirdrc.keycloak; +import com.fasterxml.jackson.databind.JsonNode; +import dev.sunbirdrc.registry.middleware.util.JSONUtil; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.KeycloakBuilder; 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; @@ -63,8 +66,9 @@ private Keycloak buildKeycloak() { .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 roles = JSONUtil.convertJsonNodeToList(realmRoles); UserRepresentation newUser = createUserRepresentation(entityName, userName, email, mobile); GroupRepresentation entityGroup = createGroupRepresentation(entityName); keycloak.realm(realm).groups().add(entityGroup); @@ -75,10 +79,11 @@ 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); 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 { @@ -86,13 +91,28 @@ public String createUser(String entityName, String userName, String email, Strin } } + private void addRolesToUser(List 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 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); + } + } + 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 roles) throws OwnerCreationException { Optional userRepresentationOptional = getUserByUsername(userName); if (userRepresentationOptional.isPresent()) { UserResource userResource = userRepresentationOptional.get(); @@ -100,6 +120,7 @@ private String updateExistingUserAttributes(String entityName, String userName, checkIfUserRegisteredForEntity(entityName, userRepresentation); updateUserAttributes(entityName, email, mobile, userRepresentation); userResource.update(userRepresentation); + addRolesToUser(roles, userName); return userRepresentation.getId(); } else { logger.error("Failed fetching user by username: {}", userName); diff --git a/java/middleware/registry-middleware/workflow/src/main/resources/workflow/statetransitions.drl b/java/middleware/registry-middleware/workflow/src/main/resources/workflow/statetransitions.drl index 1f6a878e0..b16362c9f 100644 --- a/java/middleware/registry-middleware/workflow/src/main/resources/workflow/statetransitions.drl +++ b/java/middleware/registry-middleware/workflow/src/main/resources/workflow/statetransitions.drl @@ -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 diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/EntityStateHelper.java b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/EntityStateHelper.java index b801a5aba..42e366e64 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/EntityStateHelper.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/EntityStateHelper.java @@ -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; } diff --git a/java/registry/src/test/java/dev/sunbirdrc/registry/helper/EntityStateHelperTest.java b/java/registry/src/test/java/dev/sunbirdrc/registry/helper/EntityStateHelperTest.java index 834adcdbe..bae7c4f5e 100644 --- a/java/registry/src/test/java/dev/sunbirdrc/registry/helper/EntityStateHelperTest.java +++ b/java/registry/src/test/java/dev/sunbirdrc/registry/helper/EntityStateHelperTest.java @@ -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; @@ -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()); } diff --git a/java/registry/src/test/java/dev/sunbirdrc/registry/helper/RegistryHelperTest.java b/java/registry/src/test/java/dev/sunbirdrc/registry/helper/RegistryHelperTest.java index db756b272..1b0afe39c 100644 --- a/java/registry/src/test/java/dev/sunbirdrc/registry/helper/RegistryHelperTest.java +++ b/java/registry/src/test/java/dev/sunbirdrc/registry/helper/RegistryHelperTest.java @@ -314,7 +314,7 @@ public void shouldCreateOwnersForInvite() throws Exception { JsonNode inviteJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\",\"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); @@ -328,7 +328,7 @@ public void shouldSendInviteInvitationsAfterCreatingOwners() throws Exception { JsonNode inviteJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\",\"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()); registryHelper.inviteEntity(inviteJson, ""); @@ -358,7 +358,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(); From 7a78ae082c06c472607c7c94198f786c7d68977e Mon Sep 17 00:00:00 2001 From: Shiva Rakshith Date: Mon, 27 Jun 2022 13:18:07 +0530 Subject: [PATCH 2/5] feat: add logger --- .../src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java index 88c1de978..cdefce20e 100644 --- a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java +++ b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java @@ -103,6 +103,7 @@ private void addRolesToUser(List roles, String userID){ } UserResource userResource = keycloak.realm(realm).users().get(userID); userResource.roles().realmLevel().add(roleToAdd); + logger.info("Added the following roles to keycloak user: " + roles); } } From c5033888745f94faba1e2b01aae8d8a703d6c03e Mon Sep 17 00:00:00 2001 From: Shiva Rakshith Date: Mon, 27 Jun 2022 13:24:40 +0530 Subject: [PATCH 3/5] feat: add logger --- .../main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java index cdefce20e..c7f82af5b 100644 --- a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java +++ b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java @@ -103,7 +103,9 @@ private void addRolesToUser(List roles, String userID){ } UserResource userResource = keycloak.realm(realm).users().get(userID); userResource.roles().realmLevel().add(roleToAdd); - logger.info("Added the following roles to keycloak user: " + roles); + logger.info("Added the roles: {}, to keycloak user: {}", roles, userID); + } else { + logger.info("No roles added to the user: {}", userID); } } From 590864d9e33513a252402381afdbcb0764c69b86 Mon Sep 17 00:00:00 2001 From: Shiva Rakshith Date: Mon, 27 Jun 2022 13:25:41 +0530 Subject: [PATCH 4/5] Update KeycloakAdminUtil.java --- .../src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java index c7f82af5b..6eace396f 100644 --- a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java +++ b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java @@ -103,7 +103,7 @@ private void addRolesToUser(List roles, String userID){ } UserResource userResource = keycloak.realm(realm).users().get(userID); userResource.roles().realmLevel().add(roleToAdd); - logger.info("Added the roles: {}, to keycloak user: {}", roles, userID); + logger.info("Added the roles: {}, to user: {}", roles, userID); } else { logger.info("No roles added to the user: {}", userID); } From 5bb600575b0db0505ff1546ba2d657b728b61bc2 Mon Sep 17 00:00:00 2001 From: Shiva Rakshith <80666319+shiva-rakshith@users.noreply.github.com> Date: Tue, 29 Nov 2022 17:37:56 +0530 Subject: [PATCH 5/5] fix: resolve conflicts --- .../main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java index 89f7372ac..c190a2dc9 100644 --- a/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java +++ b/java/middleware/registry-middleware/keycloak/src/main/java/dev/sunbirdrc/keycloak/KeycloakAdminUtil.java @@ -97,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 {