This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git


The following commit(s) were added to refs/heads/master by this push:
     new ec12edcc5 User portal page API and UI changes
ec12edcc5 is described below

commit ec12edcc52bd843d67a756ad48a3e7bad58fa472
Author: Ganning Xu <[email protected]>
AuthorDate: Fri Feb 28 13:35:15 2025 -0500

    User portal page API and UI changes
    
    * UI of user's page
    
    * refactor components
    
    * remove unused imports
    
    * add api level support for users page
    
    * add functions for user's page
    
    * Fix user management api routes
    
    * removed unused imports
    
    * Refactoring user apis
    
    * Fix imports
    
    * Update user endpoint works correctly. Only supports updating first/last 
name.
    
    * Add support for adding & deleting user roles
    
    * Add support for adding & deleting user attributes
    
    * Refactor get user profile methods
    
    * Only allow updating first/last name
---
 .../custos/api/user/UserManagementController.java  | 256 ++++++++++++---------
 .../custos/core/mapper/user/UserProfileMapper.java |  28 ++-
 .../custos/core/model/user/UserAttribute.java      |  24 +-
 .../apache/custos/core/model/user/UserProfile.java |  14 +-
 .../apache/custos/core/model/user/UserRole.java    |  23 +-
 .../core/repo/user/UserAttributeRepository.java    |   3 +
 .../custos/core/repo/user/UserRoleRepository.java  |   3 +
 core/src/main/proto/UserProfile.proto              |  21 ++
 custos-portal/src/components/NavContainer.tsx      |  15 ++
 .../src/components/Users/UserSettings.tsx          | 191 +++++++++------
 custos-portal/src/components/Users/index.tsx       | 125 +++++-----
 custos-portal/src/hooks/useApi.tsx                 |  33 ++-
 custos-portal/src/index.tsx                        |   4 +-
 custos-portal/src/interfaces/Users.tsx             |  10 +-
 custos-portal/src/lib/constants.ts                 |   2 +-
 custos-portal/src/lib/util.ts                      |  38 +++
 .../credential/store/CredentialStoreService.java   |  12 +-
 .../service/management/UserManagementService.java  | 168 +++++---------
 .../custos/service/profile/UserProfileService.java | 192 +++++++++++++---
 19 files changed, 751 insertions(+), 411 deletions(-)

diff --git 
a/api/src/main/java/org/apache/custos/api/user/UserManagementController.java 
b/api/src/main/java/org/apache/custos/api/user/UserManagementController.java
index 6cdb23313..8b53e7d70 100644
--- a/api/src/main/java/org/apache/custos/api/user/UserManagementController.java
+++ b/api/src/main/java/org/apache/custos/api/user/UserManagementController.java
@@ -22,12 +22,9 @@ package org.apache.custos.api.user;
 import org.apache.custos.core.constants.Constants;
 import org.apache.custos.core.iam.api.AddExternalIDPLinksRequest;
 import org.apache.custos.core.iam.api.AddUserAttributesRequest;
-import org.apache.custos.core.iam.api.AddUserRolesRequest;
 import org.apache.custos.core.iam.api.DeleteExternalIDPsRequest;
 import org.apache.custos.core.iam.api.DeleteUserAttributeRequest;
 import org.apache.custos.core.iam.api.DeleteUserRolesRequest;
-import org.apache.custos.core.iam.api.FindUsersRequest;
-import org.apache.custos.core.iam.api.FindUsersResponse;
 import org.apache.custos.core.iam.api.GetExternalIDPsRequest;
 import org.apache.custos.core.iam.api.GetExternalIDPsResponse;
 import org.apache.custos.core.iam.api.OperationStatus;
@@ -38,16 +35,20 @@ import org.apache.custos.core.iam.api.RegisterUsersResponse;
 import org.apache.custos.core.iam.api.ResetUserPassword;
 import org.apache.custos.core.iam.api.UserAttribute;
 import org.apache.custos.core.iam.api.UserRepresentation;
-import org.apache.custos.core.iam.api.UserSearchMetadata;
 import org.apache.custos.core.iam.api.UserSearchRequest;
 import org.apache.custos.core.identity.api.AuthToken;
 import org.apache.custos.core.user.management.api.LinkUserProfileRequest;
 import org.apache.custos.core.user.management.api.SynchronizeUserDBRequest;
 import org.apache.custos.core.user.management.api.UserProfileRequest;
+import org.apache.custos.core.user.profile.api.UsersRolesRequest;
+import org.apache.custos.core.user.profile.api.UsersRolesFullRequest;
 import org.apache.custos.core.user.profile.api.GetAllUserProfilesResponse;
 import org.apache.custos.core.user.profile.api.GetUpdateAuditTrailRequest;
 import org.apache.custos.core.user.profile.api.GetUpdateAuditTrailResponse;
+import org.apache.custos.core.user.profile.api.UserAttributeRequest;
+import org.apache.custos.core.user.profile.api.UserAttributeFullRequest;
 import org.apache.custos.core.user.profile.api.UserProfile;
+import org.apache.custos.core.user.profile.api.Status;
 import org.apache.custos.service.auth.AuthClaim;
 import org.apache.custos.service.auth.TokenAuthorizer;
 import org.apache.custos.service.management.UserManagementService;
@@ -69,10 +70,12 @@ import 
org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestHeader;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.server.ResponseStatusException;
 
@@ -393,35 +396,101 @@ public class UserManagementController {
         return ResponseEntity.ok(response);
     }
 
+
+    @PostMapping("/user/attributes")
+    @Operation(
+            summary = "Add attributes to a tenant user",
+            description = "This operation adds specified attributes to a 
tenant user. The id of each attribute passed in is ignored."
+    )
+    public ResponseEntity<Status> addTenantUserAttributes(@RequestBody 
UserAttributeRequest request, @RequestHeader HttpHeaders headers) {
+        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers);
+
+        if (claim.isEmpty()) {
+            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
+        }
+
+        AuthClaim authClaim = claim.get();
+
+        UserAttributeFullRequest userAttributeFullRequest = 
UserAttributeFullRequest.newBuilder()
+                .setUserAttributeRequest(request)
+                .setTenantId(authClaim.getTenantId())
+                .build();
+
+        Status status = 
userManagementService.addAttributesToUser(userAttributeFullRequest);
+
+        return ResponseEntity.ok(status);
+    }
+
+    @DeleteMapping("/user/attributes")
+    @Operation(
+            summary = "Delete attributes from a tenant user",
+            description = "This operation removes specified attributes to a 
tenant user. The id of each attribute passed in is ignored."
+    )
+    public ResponseEntity<Status> deleteTenantUserAttributes(@RequestBody 
UserAttributeRequest request, @RequestHeader HttpHeaders headers) {
+        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers);
+
+        if (claim.isEmpty()) {
+            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
+        }
+
+        AuthClaim authClaim = claim.get();
+
+        UserAttributeFullRequest userAttributeFullRequest = 
UserAttributeFullRequest.newBuilder()
+                .setUserAttributeRequest(request)
+                .setTenantId(authClaim.getTenantId())
+                .build();
+
+        Status status = 
userManagementService.deleteAttributesFromUser(userAttributeFullRequest);
+
+        return ResponseEntity.ok(status);
+    }
+
     @PostMapping("/users/roles")
     @Operation(
-            summary = "Add Roles To Users",
-            description = "This operation adds specified roles to identified 
users. The AddUserRolesRequest " +
-                    "should include user identifiers and the list of roles to 
be added. Upon successful execution, " +
-                    "the system associates the specified roles with the user 
profiles and returns an OperationStatus reflecting the result."
+            summary = "Add roles to tenant users",
+            description = "This operation adds specified roles (client & 
realm) to identified users."
     )
-    public ResponseEntity<OperationStatus> addRolesToUsers(@Valid @RequestBody 
AddUserRolesRequest request, @RequestHeader HttpHeaders headers) {
-        headers = attachUserToken(headers, request.getClientId());
-        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers, 
request.getClientId());
+    public ResponseEntity<Status> addUsersRoles(@Valid @RequestBody 
UsersRolesRequest request, @RequestHeader HttpHeaders headers) {
+        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers);
 
-        if (claim.isPresent()) {
-            AuthClaim authClaim = claim.get();
-            AuthToken authToken = 
tokenAuthorizer.getSAToken(authClaim.getIamAuthId(), 
authClaim.getIamAuthSecret(), authClaim.getTenantId());
+        if (claim.isEmpty()) {
+            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
+        }
 
-            if (authToken == null || 
StringUtils.isBlank(authToken.getAccessToken())) {
-                throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized. Service Account token is invalid");
-            }
+        AuthClaim authClaim = claim.get();
 
-            request = request.toBuilder().setClientId(authClaim.getIamAuthId())
-                    .setTenantId(authClaim.getTenantId())
-                    .setAccessToken(authToken.getAccessToken())
-                    .setPerformedBy(authClaim.getPerformedBy()).build();
-        } else {
+        UsersRolesFullRequest fullRequest = UsersRolesFullRequest.newBuilder()
+                .setUsersRoles(request)
+                .setTenantId(authClaim.getTenantId())
+                .build();
+
+        Status operationStatus = 
userManagementService.addRolesToUsers(fullRequest);
+
+        return ResponseEntity.ok(operationStatus);
+    }
+
+    @DeleteMapping("/users/roles")
+    @Operation(
+            summary = "Delete roles from tenant users",
+            description = "This operation deletes specified roles (client & 
realm) to identified users."
+    )
+    public ResponseEntity<Status> deleteUsersRoles(@Valid @RequestBody 
UsersRolesRequest request, @RequestHeader HttpHeaders headers) {
+        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers);
+
+        if (claim.isEmpty()) {
             throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
         }
 
-        OperationStatus response = 
userManagementService.addRolesToUsers(request);
-        return ResponseEntity.ok(response);
+        AuthClaim authClaim = claim.get();
+
+        UsersRolesFullRequest fullRequest = UsersRolesFullRequest.newBuilder()
+                .setUsersRoles(request)
+                .setTenantId(authClaim.getTenantId())
+                .build();
+
+        Status operationStatus = 
userManagementService.deleteRolesFromUsers(fullRequest);
+
+        return ResponseEntity.ok(operationStatus);
     }
 
     @GetMapping("/user/activation/status")
@@ -448,63 +517,6 @@ public class UserManagementController {
         return ResponseEntity.ok(response);
     }
 
-    @GetMapping("/user")
-    @Operation(
-            summary = "Retrieve User",
-            description = "This operation retrieves a specified user's 
profile. The UserSearchRequest should specify " +
-                    "the criteria to identify the particular user. It returns 
a UserRepresentation that includes " +
-                    "detailed information about the user."
-    )
-    public ResponseEntity<UserRepresentation> getUser(@Valid @RequestBody 
UserSearchRequest request, @RequestHeader HttpHeaders headers) {
-        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers, 
request.getClientId());
-
-        if (claim.isPresent()) {
-            AuthClaim authClaim = claim.get();
-            request = request.toBuilder().setClientId(authClaim.getIamAuthId())
-                    .setClientSec(authClaim.getIamAuthSecret())
-                    .setTenantId(claim.get().getTenantId()).build();
-        } else {
-            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
-        }
-
-        UserRepresentation response = userManagementService.getUser(request);
-        return ResponseEntity.ok(response);
-    }
-
-    @GetMapping("/users")
-    @Operation(
-            summary = "Find Users",
-            description = "This operation searches for users that match the 
criteria provided in the FindUsersRequest, " +
-                    "which can include attributes like username, email, roles, 
etc. It returns a FindUsersResponse " +
-                    "containing the matching users' profiles."
-    )
-    public ResponseEntity<FindUsersResponse> findUsers(@RequestParam(value = 
"client_id") String clientId,
-                                                       @RequestParam(value = 
"offset") int offset,
-                                                       @RequestParam(value = 
"limit") int limit,
-                                                       
@RequestParam("user.id") String userId,
-                                                       @RequestHeader 
HttpHeaders headers) {
-        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers, 
clientId);
-
-        if (claim.isPresent()) {
-            AuthClaim authClaim = claim.get();
-            UserSearchMetadata userSearchMetadata = 
UserSearchMetadata.newBuilder()
-                    .setId(userId)
-                    .build();
-            FindUsersRequest request = 
FindUsersRequest.newBuilder().setClientId(authClaim.getIamAuthId())
-                    .setClientSec(authClaim.getIamAuthSecret())
-                    .setTenantId(claim.get().getTenantId())
-                    .setOffset(offset)
-                    .setLimit(limit)
-                    .setUser(userSearchMetadata)
-                    .build();
-
-            FindUsersResponse response = 
userManagementService.findUsers(request);
-            return ResponseEntity.ok(response);
-        } else {
-            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
-        }
-    }
-
     @PutMapping("/user/password")
     @Operation(
             summary = "Update User Password",
@@ -570,53 +582,75 @@ public class UserManagementController {
         return ResponseEntity.ok(response);
     }
 
-    @PutMapping("/user/profile")
+    @PatchMapping("/user/profile/{username}")
     @Operation(
             summary = "Update User Profile",
-            description = "This operation updates profiles of existing users. 
The UserProfileRequest should specify the updated " +
+            description = "This operation updates profiles of existing users. 
The UserProfile should only specify fields that should be updated. Currently, 
only the first and last name can be updated." +
                     "user details. Upon successful profile update, the system 
sends back the updated UserProfile wrapped in a ResponseEntity."
     )
-    public ResponseEntity<UserProfile> updateUserProfile(@RequestBody 
UserProfileRequest request, @RequestHeader HttpHeaders headers) {
-        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers, 
request.getClientId());
+    public ResponseEntity<UserProfile> updateUserProfile(@RequestBody 
UserProfile userProfile, @PathVariable("username") String username, 
@RequestHeader HttpHeaders headers) {
+        // need to update first name, last name, that's about it?
+        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers);
 
-        if (claim.isPresent()) {
-            AuthClaim authClaim = claim.get();
-            AuthToken authToken = 
tokenAuthorizer.getSAToken(authClaim.getIamAuthId(), 
authClaim.getIamAuthSecret(), authClaim.getTenantId());
+        if (claim.isEmpty()) {
+            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
+        }
 
-            if (authToken == null || 
StringUtils.isBlank(authToken.getAccessToken())) {
-                throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized. Service Account token is invalid");
-            }
+        AuthClaim authClaim = claim.get();
+        AuthToken authToken = 
tokenAuthorizer.getSAToken(authClaim.getIamAuthId(), 
authClaim.getIamAuthSecret(), authClaim.getTenantId());
 
-            request = request.toBuilder().setClientId(authClaim.getIamAuthId())
-                    .setClientSecret(authClaim.getIamAuthSecret())
-                    .setTenantId(authClaim.getTenantId())
-                    .setAccessToken(authToken.getAccessToken())
-                    .setPerformedBy(Constants.SYSTEM).build();
-        } else {
-            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
+        if (authToken == null || 
StringUtils.isBlank(authToken.getAccessToken())) {
+            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized. Service Account token is invalid");
         }
 
+        userProfile = userProfile.toBuilder().setUsername(username).build();
+
+        UserProfileRequest request = UserProfileRequest.newBuilder()
+                .setUserProfile(userProfile)
+                .setClientId(authClaim.getIamAuthId())
+                .setClientSecret(authClaim.getIamAuthSecret())
+                .setTenantId(authClaim.getTenantId())
+                .setAccessToken(authToken.getAccessToken())
+                .setPerformedBy(Constants.SYSTEM)
+                .build();
 
         UserProfile response = 
userManagementService.updateUserProfile(request);
         return ResponseEntity.ok(response);
     }
 
-    @GetMapping("/user/profile")
+    @GetMapping("/user/profile/{username}")
     @Operation(
             summary = "Get User Profile",
-            description = "This operation retrieves the profile of a specified 
user. The UserProfileRequest should specify which user's " +
-                    "profile is to be retrieved. The system would return a 
ResponseEntity containing the UserProfile for the specified user."
+            description = "This operation retrieves the profile of a specified 
user. Parameters should be passed in the query string."
     )
-    public ResponseEntity<UserProfile> getUserProfile(@Valid @RequestBody 
UserProfileRequest request, @RequestHeader HttpHeaders headers) {
+    public ResponseEntity<UserProfile> getUserProfile(
+            @PathVariable("username") String username,
+            @RequestHeader HttpHeaders headers
+    ) {
+        // Authorization check
         Optional<AuthClaim> claim = 
tokenAuthorizer.authorizeUsingUserToken(headers);
 
+        // Build the UserProfile using the builder pattern
+        UserProfile userProfile = UserProfile.newBuilder()
+                .setUsername(username)
+                .build();
+
+        // Build the UserProfileRequest using the builder pattern
+        UserProfileRequest request = UserProfileRequest.newBuilder()
+                .setUserProfile(userProfile)
+                .setLimit(1)
+                .setOffset(0)
+                .build();
+
         if (claim.isPresent()) {
             request = 
request.toBuilder().setTenantId(claim.get().getTenantId()).build();
         } else {
             throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
         }
 
+        // Retrieve user profile
         UserProfile response = userManagementService.getUserProfile(request);
+
         return ResponseEntity.ok(response);
     }
 
@@ -649,16 +683,24 @@ public class UserManagementController {
                     "Upon successful execution, the system sends back a 
ResponseEntity containing GetAllUserProfilesResponse, " +
                     "wrapping all user profiles in the tenant."
     )
-    public ResponseEntity<GetAllUserProfilesResponse> 
getAllUserProfilesInTenant(@RequestBody UserProfileRequest request, 
@RequestHeader HttpHeaders headers) {
-        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers, 
request.getClientId());
+    public ResponseEntity<GetAllUserProfilesResponse> 
getAllUserProfilesInTenant(
+            @RequestParam(value = "offset") int offset,
+            @RequestParam(value = "limit") int limit,
+            @RequestHeader HttpHeaders headers) {
+        Optional<AuthClaim> claim = tokenAuthorizer.authorize(headers);
 
-        if (claim.isPresent()) {
-            request = 
request.toBuilder().setTenantId(claim.get().getTenantId()).build();
-        } else {
+        if (claim.isEmpty()) {
             throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, 
"Request is not authorized");
         }
 
+        UserProfileRequest request = UserProfileRequest.newBuilder()
+                .setOffset(offset)
+                .setLimit(limit)
+                .setTenantId(claim.get().getTenantId())
+                .build();
+
         GetAllUserProfilesResponse response = 
userManagementService.getAllUserProfilesInTenant(request);
+
         return ResponseEntity.ok(response);
     }
 
diff --git 
a/core/src/main/java/org/apache/custos/core/mapper/user/UserProfileMapper.java 
b/core/src/main/java/org/apache/custos/core/mapper/user/UserProfileMapper.java
index 7ae2c321b..b213eba19 100644
--- 
a/core/src/main/java/org/apache/custos/core/mapper/user/UserProfileMapper.java
+++ 
b/core/src/main/java/org/apache/custos/core/mapper/user/UserProfileMapper.java
@@ -20,9 +20,12 @@
 package org.apache.custos.core.mapper.user;
 
 import org.apache.custos.core.constants.Constants;
-import org.apache.custos.core.model.user.UserAttribute;
 import org.apache.custos.core.model.user.UserProfile;
+import org.apache.custos.core.model.user.UserAttribute;
 import org.apache.custos.core.model.user.UserRole;
+import org.apache.custos.core.model.user.GroupRole;
+import org.apache.custos.core.model.user.UserGroupMembership;
+
 import org.apache.custos.core.user.profile.api.UserStatus;
 import org.apache.custos.core.user.profile.api.UserTypes;
 
@@ -111,6 +114,29 @@ public class UserProfileMapper {
         return entity;
     }
 
+    public static org.apache.custos.core.user.profile.api.UserProfile 
createFullUserProfileFromUserProfileEntity(UserProfile profileEntity) {
+        org.apache.custos.core.user.profile.api.UserProfile userProfile = 
createUserProfileFromUserProfileEntity(profileEntity, null);
+
+        org.apache.custos.core.user.profile.api.UserProfile.Builder builder = 
userProfile.toBuilder();
+
+        if (profileEntity.getUserGroupMemberships() != null && 
!profileEntity.getUserGroupMemberships().isEmpty()) {
+            List<String> clientRoles = new ArrayList<>();
+            List<String> realmRoles = new ArrayList<>();
+            for (UserGroupMembership userGroupMembership: 
profileEntity.getUserGroupMemberships()) {
+                for (GroupRole gr: 
userGroupMembership.getGroup().getGroupRole()) {
+                    if (gr.getType().equals(Constants.ROLE_TYPE_CLIENT)) {
+                        clientRoles.add(gr.getValue());
+                    } else if (gr.getType().equals(Constants.ROLE_TYPE_REALM)) 
{
+                        realmRoles.add(gr.getValue());
+                    }
+                }
+            }
+            builder.addAllClientRoles(clientRoles);
+            builder.addAllRealmRoles(realmRoles);
+        }
+
+        return builder.build();
+    }
 
     /**
      * Creates a protobuf UserProfile object from a UserProfileEntity object.
diff --git 
a/core/src/main/java/org/apache/custos/core/model/user/UserAttribute.java 
b/core/src/main/java/org/apache/custos/core/model/user/UserAttribute.java
index e4ff844c0..18f9b80b5 100644
--- a/core/src/main/java/org/apache/custos/core/model/user/UserAttribute.java
+++ b/core/src/main/java/org/apache/custos/core/model/user/UserAttribute.java
@@ -27,9 +27,15 @@ import jakarta.persistence.Id;
 import jakarta.persistence.JoinColumn;
 import jakarta.persistence.ManyToOne;
 import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+
+import java.util.Objects;
 
 @Entity
-@Table(name = "user_attribute")
+@Table(
+        name = "user_attribute",
+        uniqueConstraints = @UniqueConstraint(columnNames = {"keyValue", 
"value", "user_profile_id"})
+)
 public class UserAttribute {
 
     @Id
@@ -46,6 +52,22 @@ public class UserAttribute {
     @JoinColumn(name = "user_profile_id")
     private UserProfile userProfile;
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        UserAttribute userRole = (UserAttribute) o;
+        return Objects.equals(id, userRole.id) &&
+                Objects.equals(keyValue, userRole.keyValue) &&
+                Objects.equals(userProfile, userRole.userProfile);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, keyValue, value, userProfile);
+    }
+
+
 
     public Long getId() {
         return id;
diff --git 
a/core/src/main/java/org/apache/custos/core/model/user/UserProfile.java 
b/core/src/main/java/org/apache/custos/core/model/user/UserProfile.java
index ae5389843..610c90850 100644
--- a/core/src/main/java/org/apache/custos/core/model/user/UserProfile.java
+++ b/core/src/main/java/org/apache/custos/core/model/user/UserProfile.java
@@ -35,6 +35,7 @@ import 
org.springframework.data.jpa.domain.support.AuditingEntityListener;
 
 import java.util.Date;
 import java.util.Set;
+import java.util.Objects;
 
 @Entity
 @Table(name = "user_profile")
@@ -76,7 +77,6 @@ public class UserProfile {
     @Column
     private String type;
 
-
     @OneToMany(fetch = FetchType.EAGER, mappedBy = "userProfile", 
orphanRemoval = true, cascade = CascadeType.ALL)
     private Set<UserRole> userRole;
 
@@ -93,6 +93,18 @@ public class UserProfile {
     @OneToMany(mappedBy = "userProfile", cascade = CascadeType.ALL)
     private Set<UserGroupMembership> userGroupMemberships;
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        UserProfile that = (UserProfile) o;
+        return id.equals(that.getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
 
     public String getId() {
         return id;
diff --git a/core/src/main/java/org/apache/custos/core/model/user/UserRole.java 
b/core/src/main/java/org/apache/custos/core/model/user/UserRole.java
index 3a5fb32e4..b278dc9f2 100644
--- a/core/src/main/java/org/apache/custos/core/model/user/UserRole.java
+++ b/core/src/main/java/org/apache/custos/core/model/user/UserRole.java
@@ -27,9 +27,15 @@ import jakarta.persistence.Id;
 import jakarta.persistence.JoinColumn;
 import jakarta.persistence.ManyToOne;
 import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+import java.util.Objects;
 
 @Entity
-@Table(name = "user_role")
+@Table(
+        name = "user_role",
+        uniqueConstraints = @UniqueConstraint(columnNames = 
{"user_profile_id", "type", "value"})
+)
+
 public class UserRole {
 
     @Id
@@ -77,4 +83,19 @@ public class UserRole {
     public void setUserProfile(UserProfile userProfile) {
         this.userProfile = userProfile;
     }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        UserRole userRole = (UserRole) o;
+        return Objects.equals(userProfile, userRole.userProfile) &&
+                Objects.equals(type, userRole.type) &&
+                Objects.equals(value, userRole.value);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(userProfile, type, value);
+    }
+
 }
diff --git 
a/core/src/main/java/org/apache/custos/core/repo/user/UserAttributeRepository.java
 
b/core/src/main/java/org/apache/custos/core/repo/user/UserAttributeRepository.java
index a54cb4437..725363217 100644
--- 
a/core/src/main/java/org/apache/custos/core/repo/user/UserAttributeRepository.java
+++ 
b/core/src/main/java/org/apache/custos/core/repo/user/UserAttributeRepository.java
@@ -25,9 +25,12 @@ import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Query;
 
 import java.util.List;
+import java.util.Optional;
 
 public interface UserAttributeRepository extends JpaRepository<UserAttribute, 
Long> {
 
     @Query("SELECT DISTINCT atr.userProfile from UserAttribute atr where 
atr.keyValue = ?1 and atr.value =?2")
     List<UserProfile> findFilteredUserProfiles(String key, String value);
+
+    Optional<UserAttribute> 
findUserAttributeByKeyValueAndValueAndUserProfile(String keyValue, String 
value, UserProfile userProfile);
 }
diff --git 
a/core/src/main/java/org/apache/custos/core/repo/user/UserRoleRepository.java 
b/core/src/main/java/org/apache/custos/core/repo/user/UserRoleRepository.java
index fd9887f72..dece92d1f 100644
--- 
a/core/src/main/java/org/apache/custos/core/repo/user/UserRoleRepository.java
+++ 
b/core/src/main/java/org/apache/custos/core/repo/user/UserRoleRepository.java
@@ -21,6 +21,9 @@ package org.apache.custos.core.repo.user;
 
 import org.apache.custos.core.model.user.UserRole;
 import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.Optional;
+import org.apache.custos.core.model.user.UserProfile;
 
 public interface UserRoleRepository extends JpaRepository<UserRole, Long> {
+    Optional<UserRole> findByTypeAndValueAndUserProfile(String type, String 
value, UserProfile userProfile);
 }
diff --git a/core/src/main/proto/UserProfile.proto 
b/core/src/main/proto/UserProfile.proto
index 5cb4429fb..e595c1be3 100644
--- a/core/src/main/proto/UserProfile.proto
+++ b/core/src/main/proto/UserProfile.proto
@@ -109,6 +109,27 @@ message GetUpdateAuditTrailResponse {
   repeated UserProfileStatusUpdateMetadata status_audit = 2;
 }
 
+message UsersRolesRequest {
+  repeated string usernames = 1;
+  repeated string roles = 2;
+  string role_type = 3;
+}
+
+message UsersRolesFullRequest {
+  int64 tenant_id = 1;
+  UsersRolesRequest users_roles = 2;
+}
+
+message UserAttributeRequest {
+  string username = 1;
+  repeated UserAttribute attributes = 2;
+}
+
+message UserAttributeFullRequest {
+  int64 tenant_id = 1;
+  UserAttributeRequest userAttributeRequest = 2;
+}
+
 message GroupRequest {
   int64 tenant_id = 1;
   Group group = 2;
diff --git a/custos-portal/src/components/NavContainer.tsx 
b/custos-portal/src/components/NavContainer.tsx
index e800485e9..ef8819cb7 100644
--- a/custos-portal/src/components/NavContainer.tsx
+++ b/custos-portal/src/components/NavContainer.tsx
@@ -48,6 +48,7 @@ import { AiOutlineAppstore } from "react-icons/ai";
 import { IconType } from "react-icons";
 import { MdLogout } from "react-icons/md";
 import { useAuth } from "react-oidc-context";
+import { BACKEND_URL } from "../lib/constants";
 
 interface NavContainerProps {
   activeTab: string;
@@ -164,6 +165,7 @@ export const NavContainer = memo(
                     _hover={{ color: "gray.500" }}
                     onClick={async () => {
                       await auth.removeUser();
+                      await auth.signoutRedirect();
                       onClose();
                     }}
                   >
@@ -248,6 +250,19 @@ export const NavContainer = memo(
                 size="sm"
                 _hover={{ color: "gray.500" }}
                 onClick={async () => {
+                  await fetch(
+                    `${BACKEND_URL}/api/v1/identity-management/user/logout`,
+                    {
+                      method: "POST",
+                      body: JSON.stringify({
+                        refresh_token: auth.user?.refresh_token,
+                      }),
+                      headers: {
+                        "Content-Type": "application/json",
+                        Authorization: `Bearer ${auth.user?.access_token}`,
+                      },
+                    }
+                  );
                   await auth.removeUser();
                 }}
               >
diff --git a/custos-portal/src/components/Users/UserSettings.tsx 
b/custos-portal/src/components/Users/UserSettings.tsx
index ba768d2ac..913082cd4 100644
--- a/custos-portal/src/components/Users/UserSettings.tsx
+++ b/custos-portal/src/components/Users/UserSettings.tsx
@@ -17,6 +17,8 @@ import {
   FormLabel,
   IconButton,
   Code,
+  Spinner,
+  HStack,
 } from "@chakra-ui/react";
 import { PageTitle } from "../PageTitle";
 import { ActionButton } from "../ActionButton";
@@ -25,42 +27,57 @@ import { FaArrowLeft } from "react-icons/fa6";
 import { LeftRightLayout } from "../LeftRightLayout";
 import { FiTrash2 } from "react-icons/fi";
 import { StackedBorderBox } from "../StackedBorderBox";
-
-const DUMMY_ROLES: any = [
-  {
-    application: "Grafana",
-    role: "grafana:viewer",
-    description: "Grafana Viewer",
-  },
-  {
-    application: "Grafana",
-    role: "grafana:editor",
-    description: "Grafana Editor",
-  },
-  {
-    application: "Grafana",
-    role: "grafana:admin",
-    description: "Grafana Admin",
-  },
-];
-
-const DUMMY_ACTIVITY: any = [
-  {
-    action: "User Created",
-    timestamp: "2021-10-01",
-  },
-  {
-    action: "User Disabled",
-    timestamp: "2021-10-01",
-  },
-  {
-    action: "User Enabled",
-    timestamp: "2021-10-01",
-  },
-];
+import { BACKEND_URL, CLIENT_ID } from "../../lib/constants";
+import { useApi } from "../../hooks/useApi";
+import { isEmpty } from "../../lib/util";
+import { useEffect, useState } from "react";
+import { useAuth } from "react-oidc-context";
 
 export const UserSettings = () => {
   const { email } = useParams();
+  const auth = useAuth();
+
+  const [user, setUser] = useState<User | null>(null);
+  const [group, setGroup] = useState<any | null>(null);
+
+  useEffect(() => {
+    async function fetchData() {
+      const userResp = await fetch(
+        `${BACKEND_URL}/api/v1/user-management/user/profile/${email}`,
+        {
+          headers: {
+            Authorization: `Bearer ${auth.user?.access_token}`,
+          },
+        }
+      );
+      const userData = await userResp.json();
+
+      const groupResp = await fetch(
+        
`${BACKEND_URL}/api/v1/group-management/users/${email}/group-memberships`,
+        {
+          headers: {
+            client_id: CLIENT_ID,
+            userId: email,
+            Authorization: `Bearer ${auth.user?.access_token}`,
+          },
+        }
+      );
+      const groupData = await groupResp.json();
+
+      setUser(userData);
+      setGroup(groupData);
+    }
+
+    fetchData();
+  }, []);
+
+  if (!user || !group) {
+    return (
+      <NavContainer activeTab="Users">
+        <Spinner />
+      </NavContainer>
+    );
+  }
 
   return (
     <>
@@ -76,9 +93,11 @@ export const UserSettings = () => {
 
         <Flex mt={4} justify="space-between">
           <Box>
-            <PageTitle>John Doe</PageTitle>
+            <PageTitle>
+              {user.first_name} {user.last_name}
+            </PageTitle>
             <Text color="default.secondary" mt={2}>
-              {email}
+              {user.email}
             </Text>
           </Box>
           <ActionButton icon={FiTrash2} onClick={() => {}}>
@@ -94,19 +113,34 @@ export const UserSettings = () => {
                 <Stack spacing={4}>
                   <FormControl color="default.default">
                     <FormLabel>Name</FormLabel>
-                    <Input type="text" />
+                    <Input
+                      type="text"
+                      value={user.first_name + " " + user.last_name}
+                    />
                   </FormControl>
                   <FormControl>
                     <FormLabel>Email</FormLabel>
-                    <Input type="text" />
+                    <Input type="text" value={user.email} />
                   </FormControl>
                   <FormControl>
                     <FormLabel>Joined</FormLabel>
-                    <Input type="text" disabled={true} />
+                    <Input
+                      type="text"
+                      disabled={true}
+                      value={new Date(
+                        parseInt(user.created_at)
+                      ).toLocaleString()}
+                    />
                   </FormControl>
                   <FormControl>
-                    <FormLabel>Last Signed In</FormLabel>
-                    <Input type="text" disabled={true} />
+                    <FormLabel>Last Modified</FormLabel>
+                    <Input
+                      type="text"
+                      disabled={true}
+                      value={new Date(
+                        parseInt(user.last_modified_at)
+                      ).toLocaleString()}
+                    />
                   </FormControl>
                 </Stack>
               </>
@@ -114,7 +148,7 @@ export const UserSettings = () => {
           />
 
           <Box>
-            <Text fontSize="lg">Groups</Text>
+            <Text fontSize="lg">Group Memberships</Text>
             <TableContainer mt={4}>
               <Table variant="simple">
                 <Thead>
@@ -148,46 +182,51 @@ export const UserSettings = () => {
           </Box>
 
           <Box>
-            <Text fontSize="lg">Roles</Text>
-            <Text mt={2} color="gray.600">
-              Through their group memberships, this user has the following 
roles
-            </Text>
+            <Text fontSize="lg">Client Roles</Text>
+            {!isEmpty(user.client_roles) ? (
+              <>
+                <Text mt={2} color="gray.600">
+                  Through their group memberships, this user has the following
+                  client roles
+                </Text>
 
-            <TableContainer mt={4}>
-              <Table variant="simple">
-                <Thead>
-                  <Tr>
-                    <Th>Application</Th>
-                    <Th>Role</Th>
-                    <Th>Description</Th>
-                  </Tr>
-                </Thead>
-                <Tbody>
-                  {DUMMY_ROLES.map((role: any) => (
-                    <Tr key={role.role}>
-                      <Td>{role.application}</Td>
-                      <Td>
-                        <Code>{role.role}</Code>
-                      </Td>
-                      <Td>{role.description}</Td>
-                    </Tr>
+                <HStack mt={2}>
+                  {user.client_roles?.map((role: string) => (
+                    <Code key={role} size="lg">
+                      {role}
+                    </Code>
                   ))}
-                </Tbody>
-              </Table>
-            </TableContainer>
+                </HStack>
+              </>
+            ) : (
+              <Text mt={2} color="gray.600">
+                This user has no client roles
+              </Text>
+            )}
           </Box>
 
           <Box>
-            <Text fontSize="lg">Activity</Text>
-            {
-              // eslint-disable-next-line @typescript-eslint/no-explicit-any
-              DUMMY_ACTIVITY.map((activity: any) => (
-                <Flex key={activity.action} gap={4} mt={4}>
-                  <Text color="gray.400">{activity.timestamp}</Text>
-                  <Text fontWeight="bold">{activity.action}</Text>
-                </Flex>
-              ))
-            }
+            <Text fontSize="lg">Realm Roles</Text>
+            {!isEmpty(user.realm_roles) ? (
+              <>
+                <Text mt={2} color="gray.600">
+                  Through their group memberships, this user has the following
+                  realm roles
+                </Text>
+
+                <HStack mt={2}>
+                  {user.client_roles?.map((role: string) => (
+                    <Code key={role} size="lg">
+                      {role}
+                    </Code>
+                  ))}
+                </HStack>
+              </>
+            ) : (
+              <Text mt={2} color="gray.600">
+                This user has no realm roles
+              </Text>
+            )}
           </Box>
         </StackedBorderBox>
       </NavContainer>
diff --git a/custos-portal/src/components/Users/index.tsx 
b/custos-portal/src/components/Users/index.tsx
index 6715c5bb9..c4fc8c851 100644
--- a/custos-portal/src/components/Users/index.tsx
+++ b/custos-portal/src/components/Users/index.tsx
@@ -14,35 +14,30 @@ import {
   Th,
   Td,
   Tbody,
+  Spinner,
 } from "@chakra-ui/react";
 import { PageTitle } from "../PageTitle";
 import { ActionButton } from "../ActionButton";
 import { CiSearch } from "react-icons/ci";
 import { User } from "../../interfaces/Users";
 import { Link } from "react-router-dom";
-
-const DUMMY_DATA: User[] = [
-  {
-    name: "Stella Zhou",
-    email: "[email protected]",
-    joined: "2021-10-01",
-    lastSignedIn: "2021-10-01",
-  },
-  {
-    name: "John Doe",
-    email: "[email protected]",
-    joined: "2021-10-01",
-    lastSignedIn: "2021-10-01",
-  },
-  {
-    name: "Jane Doe",
-    email: "[email protected]",
-    joined: "2021-10-01",
-    lastSignedIn: "2021-10-01",
-  },
-];
+import { useApi } from "../../hooks/useApi";
+import { BACKEND_URL } from "../../lib/constants";
+import { timeAgo } from "../../lib/util";
 
 export const Users = () => {
+  const offset = 0;
+  const limit = 10;
+
+
+  const urlSearchParams = new URLSearchParams();
+  urlSearchParams.append("offset", offset.toString());
+  urlSearchParams.append("limit", limit.toString());
+
+  const allUsers = useApi(
+    
`${BACKEND_URL}/api/v1/user-management/users/profile?${urlSearchParams.toString()}`
+  );
+    
   return (
     <>
       <NavContainer activeTab="Users">
@@ -72,45 +67,57 @@ export const Users = () => {
         </InputGroup>
 
         {/* TABLE */}
-        <TableContainer mt={4}>
-          <Table variant="simple">
-            <Thead>
-              <Tr>
-                <Th>Name</Th>
-                <Th>Email</Th>
-                <Th>Joined</Th>
-                <Th>Last Signed In</Th>
-                <Th>Actions</Th>
-              </Tr>
-            </Thead>
-
-            <Tbody>
-              {DUMMY_DATA.map((user) => (
-                <Tr key={user.email}>
-                  <Td>
-                    <Link to={`/users/${user.email}`}>
-                      <Text
-                        color="blue.400"
-                        _hover={{
-                          color: "blue.600",
-                          cursor: "pointer",
-                        }}
-                      >
-                        {user.name}
-                      </Text>
-                    </Link>
-                  </Td>
-                  <Td>{user.email}</Td>
-                  <Td>{user.joined}</Td>
-                  <Td>{user.lastSignedIn}</Td>
-                  <Td>
-                    <ActionButton onClick={() => {}}>Disable</ActionButton>
-                  </Td>
+        {allUsers?.isPending ? (
+          <Box textAlign="center" mt={4}>
+            <Spinner />
+          </Box>
+        ) : (
+          <TableContainer mt={4}>
+            <Table variant="simple">
+              <Thead>
+                <Tr>
+                  <Th>Name</Th>
+                  <Th>Email</Th>
+                  <Th>Joined</Th>
+                  <Th>Last Modified</Th>
+                  <Th>Actions</Th>
                 </Tr>
-              ))}
-            </Tbody>
-          </Table>
-        </TableContainer>
+              </Thead>
+
+              <Tbody>
+                {allUsers?.data?.profiles?.map((user: User) => (
+                  <Tr key={user.email}>
+                    <Td>
+                      <Link to={`/users/${user.email}`}>
+                        <Text
+                          color="blue.400"
+                          _hover={{
+                            color: "blue.600",
+                            cursor: "pointer",
+                          }}
+                        >
+                          {user.first_name} {user.last_name}
+                        </Text>
+                      </Link>
+                    </Td>
+                    <Td>{user.email}</Td>
+                    <Td>
+                      {new Date(parseInt(user.created_at)).toDateString()}
+                    </Td>
+                    <Td>
+                      {user.last_modified_at
+                        ? timeAgo(new Date(parseInt(user.last_modified_at)))
+                        : "N/A"}
+                    </Td>
+                    <Td>
+                      <ActionButton onClick={() => {}}>Disable</ActionButton>
+                    </Td>
+                  </Tr>
+                ))}
+              </Tbody>
+            </Table>
+          </TableContainer>
+        )}
       </NavContainer>
     </>
   );
diff --git a/custos-portal/src/hooks/useApi.tsx 
b/custos-portal/src/hooks/useApi.tsx
index 3acdeb30b..4842ffbcf 100644
--- a/custos-portal/src/hooks/useApi.tsx
+++ b/custos-portal/src/hooks/useApi.tsx
@@ -22,13 +22,20 @@ import { useAuth } from "react-oidc-context";
 
 export const useApi = (url: string, options?: RequestInit) => {
   const auth = useAuth();
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
   const [data, setData] = useState<any | null>(null);
   const [isPending, setIsPending] = useState(false);
   const [error, setError] = useState("");
+
   useEffect(() => {
+    if (!url) return; // Avoid calling fetch if the URL is empty
+
+    const controller = new AbortController();
+    const signal = controller.signal;
+
     const fetchData = async () => {
       setIsPending(true);
+      setError(""); // Reset error before fetching
+
       try {
         const response = await fetch(url, {
           ...options,
@@ -36,18 +43,30 @@ export const useApi = (url: string, options?: RequestInit) 
=> {
             ...options?.headers,
             Authorization: `Bearer ${auth.user?.access_token}`,
           },
+          signal, // Pass the signal to fetch
         });
+
         if (!response.ok) throw new Error(response.statusText);
         const json = await response.json();
-        setIsPending(false);
-        setData(json);
-        setError("");
-      } catch (error) {
-        setError(`${error} Could not Fetch Data.`);
-        setIsPending(false);
+
+        if (!signal.aborted) {
+          setData(json);
+        }
+      } catch (err) {
+        if (!signal.aborted) {
+          setError(`Error: ${err.message} - Could not fetch data.`);
+        }
+      } finally {
+        if (!signal.aborted) {
+          setIsPending(false);
+        }
       }
     };
+
     fetchData();
+
+    return () => controller.abort(); // Cleanup on unmount or dependency change
   }, [auth.user?.access_token, options, url]);
+
   return { data, isPending, error };
 };
diff --git a/custos-portal/src/index.tsx b/custos-portal/src/index.tsx
index b4e855719..88f07776a 100644
--- a/custos-portal/src/index.tsx
+++ b/custos-portal/src/index.tsx
@@ -53,11 +53,11 @@ const Index = () => {
   useEffect(() => {
     const fetchOidcConfig = async () => {
       try {
-        let data;
         const response = await fetch(
           
`${BACKEND_URL}/api/v1/identity-management/tenant/${TENANT_ID}/.well-known/openid-configuration`
         ); // Replace with actual API endpoint
-        data = await response.json();
+        const data = await response.json();
+
         const redirectUri = APP_REDIRECT_URI;
 
         const theConfig: AuthProviderProps = {
diff --git a/custos-portal/src/interfaces/Users.tsx 
b/custos-portal/src/interfaces/Users.tsx
index 83bf47471..ec587e56a 100644
--- a/custos-portal/src/interfaces/Users.tsx
+++ b/custos-portal/src/interfaces/Users.tsx
@@ -1,6 +1,10 @@
 export interface User {
-  name: string;
+  username: string;
   email: string;
-  joined: string;
-  lastSignedIn: string;
+  first_name: string;
+  last_name: string;
+  created_at: string;
+  client_roles: string[];
+  realm_roles: string[];
+  last_modified_at: string;
 }
diff --git a/custos-portal/src/lib/constants.ts 
b/custos-portal/src/lib/constants.ts
index 652cc4cd5..a61907dc1 100644
--- a/custos-portal/src/lib/constants.ts
+++ b/custos-portal/src/lib/constants.ts
@@ -20,7 +20,7 @@
 import packageJson from '../../package.json';
 
 export const PORTAL_VERSION = packageJson.version;
-export const CLIENT_ID = 'custos-gcq8jxkwpvs2gcudzmfn-10000000';;
+export const CLIENT_ID = 'custos-2o7lfqdfsfxhjdp6qmxi-10000000';
 export const BACKEND_URL = 'http://localhost:8081';
 export const APP_URL = "http://localhost:5173";;
 
diff --git a/custos-portal/src/lib/util.ts b/custos-portal/src/lib/util.ts
index 4340d1d3d..d3480cba1 100644
--- a/custos-portal/src/lib/util.ts
+++ b/custos-portal/src/lib/util.ts
@@ -21,3 +21,41 @@ export const decodeToken = (token: string | undefined) => {
   if (!token) return null;
   return JSON.parse(atob(token.split('.')[1]));
 };
+
+export function timeAgo(date: number) {
+  const now = new Date();
+  const seconds = Math.floor((now - date) / 1000);
+
+  if (seconds < 60) {
+    return `${seconds} second${seconds !== 1 ? "s" : ""} ago`;
+  }
+
+  const minutes = Math.floor(seconds / 60);
+  if (minutes < 60) {
+    return `${minutes} minute${minutes !== 1 ? "s" : ""} ago`;
+  }
+
+  const hours = Math.floor(minutes / 60);
+  if (hours < 24) {
+    return `${hours} hour${hours !== 1 ? "s" : ""} ago`;
+  }
+
+  const days = Math.floor(hours / 24);
+  if (days < 30) {
+    return `${days} day${days !== 1 ? "s" : ""} ago`;
+  }
+
+  const months = Math.floor(days / 30);
+  if (months < 12) {
+    return `${months} month${months !== 1 ? "s" : ""} ago`;
+  }
+
+  const years = Math.floor(months / 12);
+  return `${years} year${years !== 1 ? "s" : ""} ago`;
+}
+
+export function isEmpty(arr: unknown[]) {
+  if (!arr) return true;
+
+  return arr.length == 0;
+}
\ No newline at end of file
diff --git 
a/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
 
b/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
index 61b8f5ff9..8157455fb 100644
--- 
a/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
+++ 
b/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
@@ -405,7 +405,7 @@ public class CredentialStoreService {
     public GetAllCredentialsResponse getAllCredentialsFromToken(TokenRequest 
request) {
         try {
             String token = request.getToken();
-            Credential credential = credentialManager.decodeToken(token);
+            Credential credential = credentialManager.decodeJWTToken(token);
 
             if (credential == null || credential.getId() == null) {
                 LOGGER.error("Invalid access token");
@@ -420,16 +420,6 @@ public class CredentialStoreService {
             }
 
             String subPath = BASE_PATH + entity.getOwnerId();
-
-            String validatingPath = BASE_PATH + entity.getOwnerId() + "/" + 
Type.CUSTOS.name();
-            VaultResponseSupport<Credential> validationResponse = 
vaultTemplate.read(validatingPath, Credential.class);
-
-            if (validationResponse == null || validationResponse.getData() == 
null || 
!validationResponse.getData().getSecret().equals(credential.getSecret())) {
-                String msg = "Invalid secret for Id: " + credential.getId();
-                LOGGER.error(msg);
-                throw new AuthenticationException(msg);
-            }
-
             List<String> paths = vaultTemplate.list(subPath);
 
             List<CredentialMetadata> credentialMetadata = new ArrayList<>();
diff --git 
a/services/src/main/java/org/apache/custos/service/management/UserManagementService.java
 
b/services/src/main/java/org/apache/custos/service/management/UserManagementService.java
index 2dca61c6d..1099482ab 100644
--- 
a/services/src/main/java/org/apache/custos/service/management/UserManagementService.java
+++ 
b/services/src/main/java/org/apache/custos/service/management/UserManagementService.java
@@ -24,13 +24,10 @@ import com.nimbusds.jwt.SignedJWT;
 import org.apache.custos.core.constants.Constants;
 import org.apache.custos.core.iam.api.AddExternalIDPLinksRequest;
 import org.apache.custos.core.iam.api.AddUserAttributesRequest;
-import org.apache.custos.core.iam.api.AddUserRolesRequest;
 import org.apache.custos.core.iam.api.CheckingResponse;
 import org.apache.custos.core.iam.api.DeleteExternalIDPsRequest;
 import org.apache.custos.core.iam.api.DeleteUserAttributeRequest;
 import org.apache.custos.core.iam.api.DeleteUserRolesRequest;
-import org.apache.custos.core.iam.api.FindUsersRequest;
-import org.apache.custos.core.iam.api.FindUsersResponse;
 import org.apache.custos.core.iam.api.GetAllResources;
 import org.apache.custos.core.iam.api.GetAllResourcesResponse;
 import org.apache.custos.core.iam.api.GetExternalIDPsRequest;
@@ -57,6 +54,9 @@ import 
org.apache.custos.core.user.profile.api.GetUpdateAuditTrailRequest;
 import org.apache.custos.core.user.profile.api.GetUpdateAuditTrailResponse;
 import org.apache.custos.core.user.profile.api.UserProfile;
 import org.apache.custos.core.user.profile.api.UserStatus;
+import org.apache.custos.core.user.profile.api.UsersRolesFullRequest;
+import org.apache.custos.core.user.profile.api.Status;
+import org.apache.custos.core.user.profile.api.UserAttributeFullRequest;
 import org.apache.custos.service.exceptions.AuthenticationException;
 import org.apache.custos.service.exceptions.InternalServerException;
 import org.apache.custos.service.iam.IamAdminService;
@@ -516,49 +516,6 @@ public class UserManagementService {
         }
     }
 
-    /**
-     * Finds users based on the provided search request.
-     *
-     * @param request the request object containing the user search criteria
-     * @return the response object containing the found users
-     * @throws AuthenticationException if an authentication exception occurs
-     * @throws InternalServerException if an internal server exception occurs
-     */
-    public FindUsersResponse findUsers(FindUsersRequest request) {
-        try {
-            long initiationTime = System.currentTimeMillis();
-            GetUserManagementSATokenRequest userManagementSATokenRequest = 
GetUserManagementSATokenRequest.newBuilder()
-                    .setClientId(request.getClientId())
-                    .setClientSecret(request.getClientSec())
-                    .setTenantId(request.getTenantId())
-                    .build();
-            AuthToken token = 
identityService.getUserManagementServiceAccountAccessToken(userManagementSATokenRequest);
-
-            if (token != null && token.getAccessToken() != null) {
-
-                request = 
request.toBuilder().setAccessToken(token.getAccessToken()).build();
-                FindUsersResponse user = iamAdminService.findUsers(request);
-                long endTime = System.currentTimeMillis();
-                long total = endTime - initiationTime;
-                LOGGER.debug("request received: " + initiationTime + " request 
end time" + endTime + " difference " + total);
-                return user;
-
-            } else {
-                LOGGER.error("Cannot find service token");
-                throw new RuntimeException("Cannot find service token");
-            }
-
-        } catch (Exception ex) {
-            String msg = "Error occurred while pulling users, " + 
ex.getMessage();
-            LOGGER.error(msg);
-            if (ex.getMessage().contains("UNAUTHENTICATED")) {
-                throw new AuthenticationException(msg, ex);
-            } else {
-                throw new InternalServerException(msg, ex);
-            }
-        }
-    }
-
     /**
      * Resets the password for a user.
      *
@@ -592,61 +549,61 @@ public class UserManagementService {
         }
     }
 
-    /**
-     * Adds roles to users based on the provided request.
-     *
-     * @param request the request object containing the information to add 
roles to users
-     * @return the operation status indicating the result of adding roles to 
users
-     * @throws AuthenticationException if an authentication exception occurs
-     * @throws InternalServerException if an internal server exception occurs
-     */
-    public OperationStatus addRolesToUsers(AddUserRolesRequest request) {
+    public Status addAttributesToUser(UserAttributeFullRequest request) {
         try {
-            OperationStatus response = 
iamAdminService.addRolesToUsers(request);
-
-            for (String user : request.getUsernamesList()) {
-                UserSearchMetadata metadata = UserSearchMetadata
-                        .newBuilder()
-                        .setUsername(user).build();
-
-                UserSearchRequest searchRequest = UserSearchRequest
-                        .newBuilder()
-                        .setClientId(request.getClientId())
-                        .setTenantId(request.getTenantId())
-                        .setAccessToken(request.getAccessToken())
-                        .setUser(metadata)
-                        .build();
-
-                UserRepresentation representation = 
iamAdminService.getUser(searchRequest);
+            userProfileService.addUserAttributes(request);
+            return Status.newBuilder().setStatus(true).build();
+        } catch(Exception ex) {
+            String msg = "Error occurred while adding attributes: " + 
ex.getMessage();
+            LOGGER.error(msg);
+            throw new InternalServerException(msg, ex);
+        }
+    }
 
-                if (representation != null) {
-                    UserProfile profile = 
this.convertToProfile(representation);
+    public Status deleteAttributesFromUser(UserAttributeFullRequest request) {
+        try {
+            userProfileService.deleteUserAttributes(request);
+            return Status.newBuilder().setStatus(true).build();
+        } catch(Exception ex) {
+            String msg = "Error occurred while adding attributes: " + 
ex.getMessage();
+            LOGGER.error(msg);
+            throw new InternalServerException(msg, ex);
+        }
+    }
 
-                    org.apache.custos.core.user.profile.api.UserProfileRequest 
req = org.apache.custos.core.user.profile.api.UserProfileRequest.newBuilder()
-                            .setTenantId(request.getTenantId())
-                            .setProfile(profile)
-                            .build();
+    public Status addRolesToUsers(UsersRolesFullRequest request) {
+        try {
+            String[] usernames = 
request.getUsersRoles().getUsernamesList().toArray(new String[0]);
+            String[] roles = 
request.getUsersRoles().getRolesList().toArray(new String[0]);
 
+            for (String username : usernames) {
+                for (String role: roles) {
+                     userProfileService.addUserRole(username, role, 
request.getUsersRoles().getRoleType(), request.getTenantId());
+                }
+            }
+            return Status.newBuilder().setStatus(true).build();
+        } catch(Exception ex) {
+            String msg = "Error occurred while adding roles: " + 
ex.getMessage();
+            LOGGER.error(msg);
+            throw new InternalServerException(msg, ex);
+        }
+    }
 
-                    UserProfile existingUser = 
userProfileService.getUserProfile(req);
+    public Status deleteRolesFromUsers(UsersRolesFullRequest request) {
+        try {
+            String[] usernames = 
request.getUsersRoles().getUsernamesList().toArray(new String[0]);
+            String[] roles = 
request.getUsersRoles().getRolesList().toArray(new String[0]);
 
-                    if (existingUser == null || 
StringUtils.isBlank(existingUser.getUsername())) {
-                        userProfileService.createUserProfile(req);
-                    } else {
-                        userProfileService.updateUserProfile(req);
-                    }
+            for (String username : usernames) {
+                for (String role: roles) {
+                    userProfileService.deleteUserRole(username, role, 
request.getUsersRoles().getRoleType(), request.getTenantId());
                 }
             }
-            return response;
-
-        } catch (Exception ex) {
-            String msg = "Error occurred while adding roles to users, " + 
ex.getMessage();
+            return Status.newBuilder().setStatus(true).build();
+        } catch(Exception ex) {
+            String msg = "Error occurred while adding deleting roles: " + 
ex.getMessage();
             LOGGER.error(msg);
-            if (ex.getMessage().contains("UNAUTHENTICATED")) {
-                throw new AuthenticationException(msg, ex);
-            } else {
-                throw new InternalServerException(msg, ex);
-            }
+            throw new InternalServerException(msg, ex);
         }
     }
 
@@ -763,7 +720,7 @@ public class UserManagementService {
     }
 
     /**
-     * Updates the user profile for the given user.
+     * Updates the user profile (in tenant and keycloak) for the given user.
      *
      * @param request The user profile request containing the updated user 
profile information.
      * @return The updated user profile.
@@ -785,7 +742,6 @@ public class UserManagementService {
 
             CheckingResponse response = iamAdminService.isUserExist(info);
 
-
             if (!response.getIsExist()) {
                 String msg = "User not found with username " + 
request.getUserProfile().getUsername();
                 LOGGER.error(msg);
@@ -798,7 +754,6 @@ public class UserManagementService {
                     .toBuilder()
                     .setFirstName(request.getUserProfile().getFirstName())
                     .setLastName(request.getUserProfile().getLastName())
-                    .setEmail(request.getUserProfile().getEmail())
                     .build();
 
             UpdateUserProfileRequest updateUserProfileRequest = 
UpdateUserProfileRequest
@@ -822,25 +777,20 @@ public class UserManagementService {
 
                     if (profile != null && 
StringUtils.isNotBlank(profile.getUsername())) {
                         profile = profile.toBuilder()
-                                .setEmail(request.getUserProfile().getEmail())
                                 
.setFirstName(request.getUserProfile().getFirstName())
                                 
.setLastName(request.getUserProfile().getLastName())
-                                
.setUsername(request.getUserProfile().getUsername())
                                 .build();
                         userProfileRequest = 
userProfileRequest.toBuilder().setProfile(profile).build();
                         
userProfileService.updateUserProfile(userProfileRequest);
-                        return profile;
-
+                        return 
userProfileService.getFullUserProfile(userProfileRequest);
                     } else {
                         UserProfile userProfile = UserProfile.newBuilder()
-                                .setEmail(request.getUserProfile().getEmail())
                                 
.setFirstName(request.getUserProfile().getFirstName())
                                 
.setLastName(request.getUserProfile().getLastName())
-                                
.setUsername(request.getUserProfile().getUsername())
                                 .build();
                         userProfileRequest = 
userProfileRequest.toBuilder().setProfile(userProfile).build();
                         
userProfileService.createUserProfile(userProfileRequest);
-                        return profile;
+                        return 
userProfileService.getFullUserProfile(userProfileRequest);
                     }
 
                 } catch (Exception ex) {
@@ -911,7 +861,7 @@ public class UserManagementService {
     }
 
     /**
-     * Retrieves the user profile based on the provided request.
+     * Retrieves the full user profile based on the provided request 
(including inherited group roles)
      *
      * @param request the request object containing the user profile search 
criteria
      * @return the user profile object corresponding to the retrieved profile
@@ -927,7 +877,7 @@ public class UserManagementService {
                     .setTenantId(request.getTenantId())
                     .build();
 
-            return userProfileService.getUserProfile(userProfileRequest);
+            return userProfileService.getFullUserProfile(userProfileRequest);
 
         } catch (Exception ex) {
             String msg = "Error occurred while pulling  user profile " + 
ex.getMessage();
@@ -949,15 +899,17 @@ public class UserManagementService {
 
             org.apache.custos.core.user.profile.api.UserProfileRequest 
userProfileRequest = org.apache.custos.core.user.profile.api.UserProfileRequest
                     .newBuilder()
-                    .setProfile(request.getUserProfile())
+//                    .setProfile(request.getUserProfile())
                     .setTenantId(request.getTenantId())
                     .setOffset(request.getOffset())
                     .setLimit(request.getLimit())
                     .build();
 
-            return request.getUserProfile().getAttributesList().isEmpty()
-                    ? 
userProfileService.getAllUserProfilesInTenant(userProfileRequest)
-                    : 
userProfileService.findUserProfilesByAttributes(userProfileRequest);
+            return 
userProfileService.getAllUserProfilesInTenant(userProfileRequest);
+
+//            return request.getUserProfile().getAttributesList().isEmpty()
+//                    ? 
userProfileService.getAllUserProfilesInTenant(userProfileRequest)
+//                    : 
userProfileService.findUserProfilesByAttributes(userProfileRequest);
 
         } catch (Exception ex) {
             String msg = "Error occurred while pulling  all  user profiles in 
tenant " + ex.getMessage();
diff --git 
a/services/src/main/java/org/apache/custos/service/profile/UserProfileService.java
 
b/services/src/main/java/org/apache/custos/service/profile/UserProfileService.java
index 83ff2b51a..758f9e609 100644
--- 
a/services/src/main/java/org/apache/custos/service/profile/UserProfileService.java
+++ 
b/services/src/main/java/org/apache/custos/service/profile/UserProfileService.java
@@ -52,6 +52,7 @@ import 
org.apache.custos.core.user.profile.api.GroupMembership;
 import org.apache.custos.core.user.profile.api.GroupRequest;
 import org.apache.custos.core.user.profile.api.Status;
 import org.apache.custos.core.user.profile.api.UserAttribute;
+import org.apache.custos.core.user.profile.api.UserAttributeFullRequest;
 import org.apache.custos.core.user.profile.api.UserGroupMembershipTypeRequest;
 import 
org.apache.custos.core.user.profile.api.UserProfileAttributeUpdateMetadata;
 import org.apache.custos.core.user.profile.api.UserProfileRequest;
@@ -113,6 +114,126 @@ public class UserProfileService {
     @Autowired
     private GroupMembershipTypeRepository groupMembershipTypeRepository;
 
+    public void addUserAttributes(UserAttributeFullRequest request) {
+        String userId = request.getUserAttributeRequest().getUsername() + "@" 
+ request.getTenantId();
+
+        Optional<UserProfile> opProfile = repository.findById(userId);
+        if (opProfile.isEmpty()) {
+            throw new EntityNotFoundException("Could not find the UserProfile 
with the id: " + userId);
+        }
+        UserProfile profileEntity = opProfile.get();
+
+        for (UserAttribute userAttribute : 
request.getUserAttributeRequest().getAttributesList()) {
+            for (String value : userAttribute.getValuesList()) {
+                org.apache.custos.core.model.user.UserAttribute 
userAttributeEntity = new org.apache.custos.core.model.user.UserAttribute();
+                userAttributeEntity.setUserProfile(profileEntity);
+                userAttributeEntity.setKey(userAttribute.getKey());
+                userAttributeEntity.setValue(value);
+
+                userAttributeRepository.save(userAttributeEntity);
+            }
+        }
+    }
+
+    public void deleteUserAttributes(UserAttributeFullRequest request) {
+        String userId = request.getUserAttributeRequest().getUsername() + "@" 
+ request.getTenantId();
+
+        Optional<UserProfile> opProfile = repository.findById(userId);
+        if (opProfile.isEmpty()) {
+            throw new EntityNotFoundException("Could not find the UserProfile 
with the id: " + userId);
+        }
+        UserProfile profileEntity = opProfile.get();
+
+        for (UserAttribute userAttribute : 
request.getUserAttributeRequest().getAttributesList()) {
+            for (String value : userAttribute.getValuesList()) {
+
+                Optional<org.apache.custos.core.model.user.UserAttribute> 
opUserAttribute = 
userAttributeRepository.findUserAttributeByKeyValueAndValueAndUserProfile(
+                        userAttribute.getKey(),
+                        value,
+                        profileEntity
+                );
+
+                if (opUserAttribute.isEmpty()) {
+                    throw new EntityNotFoundException("Could not find the user 
attribute with key " + userAttribute.getKey() + " and value " + value + " for 
user " + userId);
+                }
+
+                profileEntity.getUserAttribute().remove(opUserAttribute.get());
+
+                repository.save(profileEntity); // cascade deletion
+            }
+        }
+
+
+    }
+
+    private boolean isValidRoleType(String type) {
+        return 
(type.equals(org.apache.custos.core.constants.Constants.ROLE_TYPE_CLIENT) || 
type.equals(org.apache.custos.core.constants.Constants.ROLE_TYPE_REALM));
+    }
+
+    public boolean addUserRole(String username, String role, String type, long 
tenantId) {
+        try {
+            if (!isValidRoleType(type)) {
+                throw new IllegalArgumentException("Role type must be `client` 
or `realm`");
+            }
+
+            String userId = username + "@" + tenantId;
+
+            Optional<UserProfile> op = repository.findById(userId);
+
+            if (op.isEmpty()) {
+                throw new EntityNotFoundException("Could not find the 
UserProfile with the id: " + userId);
+            }
+
+            UserProfile userProfile = op.get();
+
+            org.apache.custos.core.model.user.UserRole userRole = new 
org.apache.custos.core.model.user.UserRole();
+            userRole.setType(type);
+            userRole.setValue(role);
+            userRole.setUserProfile(userProfile);
+
+            roleRepository.save(userRole);
+
+            return true;
+        } catch(Exception ex) {
+            String msg = "Error occurred while adding role " + role + " for " 
+ username + " reason: " + ex.getMessage();
+            LOGGER.error(msg);
+            throw new RuntimeException(msg, ex);
+        }
+    }
+
+    public boolean deleteUserRole(String username, String role, String type, 
long tenantId) {
+        try {
+            if (!isValidRoleType(type)) {
+                throw new IllegalArgumentException("Role type must be `client` 
or `realm`");
+            }
+
+            String userId = username + "@" + tenantId;
+
+            Optional<UserProfile> op = repository.findById(userId);
+
+            if (op.isEmpty()) {
+                throw new EntityNotFoundException("Could not find the 
UserProfile with the id: " + userId);
+            }
+
+            UserProfile userProfile = op.get();
+
+            Optional<org.apache.custos.core.model.user.UserRole> 
optionalUserRole = roleRepository.findByTypeAndValueAndUserProfile(type, role, 
userProfile);
+
+            if (optionalUserRole.isEmpty()) {
+                throw new EntityNotFoundException("User role " + role + "(" + 
type + ") not found for user: " + userId + ". To remove roles inherited from 
groups, please remove the user from the group.");
+            }
+
+            userProfile.getUserRole().remove(optionalUserRole.get());
+
+            repository.save(userProfile); // trigger orphan removal
+
+            return true;
+        } catch(Exception ex) {
+            String msg = "Error occurred while removing role " + role + " for 
" + username + " reason: " + ex.getMessage();
+            LOGGER.error(msg);
+            throw new RuntimeException(msg, ex);
+        }
+    }
 
     public org.apache.custos.core.user.profile.api.UserProfile 
createUserProfile(UserProfileRequest request) {
         try {
@@ -130,7 +251,6 @@ public class UserProfileService {
             }
 
             return request.getProfile();
-
         } catch (Exception ex) {
             String msg = "Error occurred while creating user profile for " + 
request.getProfile().getUsername() + "at "
                     + request.getTenantId() + " reason :" + ex.getMessage();
@@ -145,35 +265,19 @@ public class UserProfileService {
 
             String userId = request.getProfile().getUsername() + "@" + 
request.getTenantId();
 
-            Optional<UserProfile> exEntity = repository.findById(userId);
-
-
-            if (exEntity.isPresent()) {
-                UserProfile entity = 
UserProfileMapper.createUserProfileEntityFromUserProfile(request.getProfile());
-                Set<AttributeUpdateMetadata> metadata = 
AttributeUpdateMetadataMapper.
-                        createAttributeUpdateMetadataEntity(exEntity.get(), 
entity, request.getPerformedBy());
-
-                entity.setAttributeUpdateMetadata(metadata);
-                entity.setId(userId);
-                entity.setTenantId(request.getTenantId());
-                entity.setCreatedAt(exEntity.get().getCreatedAt());
+            Optional<UserProfile> opExEntity = repository.findById(userId);
 
-                UserProfile exProfile = exEntity.get();
+            if (opExEntity.isPresent()) {
+                UserProfile exEntity = opExEntity.get();
 
-                if (exProfile.getUserAttribute() != null) {
-                    
userAttributeRepository.deleteAll(exProfile.getUserAttribute());
-                }
+                exEntity.setFirstName(request.getProfile().getFirstName());
+                exEntity.setLastName(request.getProfile().getLastName());
 
-                if (exProfile.getUserRole() != null) {
-                    roleRepository.deleteAll(exProfile.getUserRole());
-                }
+                repository.save(exEntity);
 
-                repository.save(entity);
                 return request.getProfile();
-
             } else {
-                LOGGER.error("Cannot find a user profile for " + userId);
-                throw new EntityNotFoundException("Cannot find a user profile 
for " + userId);
+                throw new EntityNotFoundException("Can't find user profile");
             }
 
         } catch (Exception ex) {
@@ -184,24 +288,46 @@ public class UserProfileService {
         }
     }
 
+    /**
+     * Returns the user profile, not including any inherited group roles
+     * @param request Must specify profile.username
+     * @return The user profile
+     */
     public org.apache.custos.core.user.profile.api.UserProfile 
getUserProfile(UserProfileRequest request) {
         try {
             LOGGER.debug("Request received to getUserProfile for " + 
request.getProfile().getUsername() + "at " + request.getTenantId());
-
             String userId = request.getProfile().getUsername() + "@" + 
request.getTenantId();
-
             Optional<UserProfile> entity = repository.findById(userId);
 
-            if (entity.isPresent()) {
-                UserProfile profileEntity = entity.get();
-                return 
UserProfileMapper.createUserProfileFromUserProfileEntity(profileEntity, null);
-
-            } else {
-                return null;
+            if (entity.isEmpty()) {
+                throw new EntityNotFoundException("Could not find the 
UserProfile with the id: " + userId);
             }
 
+            return 
UserProfileMapper.createUserProfileFromUserProfileEntity(entity.get(), null);
         } catch (Exception ex) {
-            String msg = "Error occurred while fetching user profile for " + 
request.getProfile().getUsername() + "at " + request.getTenantId();
+            String msg = "Error occurred while updating user profile for " + 
request.getProfile().getUsername() + " at "
+                    + request.getTenantId() + " reason: " + ex.getMessage();
+            LOGGER.error(msg);
+            throw new RuntimeException(msg, ex);
+        }
+    }
+
+    /**
+     * Returns the full user profile, including roles inherited from groups
+     * @param request Must specify profile.username
+     * @return The full user profile
+     */
+    public org.apache.custos.core.user.profile.api.UserProfile 
getFullUserProfile(UserProfileRequest request) {
+        try {
+            LOGGER.debug("Request received to getFullUserProfile for " + 
request.getProfile().getUsername() + "@ " + request.getTenantId());
+            String userId = request.getProfile().getUsername() + "@" + 
request.getTenantId();
+            Optional<UserProfile> entity = repository.findById(userId);
+
+            return 
entity.map(UserProfileMapper::createFullUserProfileFromUserProfileEntity)
+                    .orElseThrow(() -> new EntityNotFoundException("Could not 
find the UserProfile with the id: " + userId));
+        } catch (Exception ex) {
+            String msg = "Error occurred while updating user profile for " + 
request.getProfile().getUsername() + " at "
+                    + request.getTenantId() + " reason: " + ex.getMessage();
             LOGGER.error(msg);
             throw new RuntimeException(msg, ex);
         }

Reply via email to