Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions src/main/java/com/ase/userservice/security/UserInformationJWT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package com.ase.userservice.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* Helper class to extract user information from JWT tokens.
* Provides convenient static methods to access authenticated user data.
*/
public class UserInformationJWT {

/**
* Get the current JWT token from the security context
* @return JWT token or null if not authenticated
*/
private static Jwt getCurrentJwt() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication instanceof JwtAuthenticationToken) {
return ((JwtAuthenticationToken) authentication).getToken();
}

return null;
}

/**
* Get the user ID
* @return User ID or null if not available
*/
public static String getUserId() {
Jwt jwt = getCurrentJwt();
return jwt != null ? jwt.getSubject() : null;
}

/**
* Get the user's email address
* @return Email or null if not available
*/
public static String getEmail() {
Jwt jwt = getCurrentJwt();
return jwt != null ? jwt.getClaimAsString("email") : null;
}

/**
* Get the username
* @return Username or null if not available
*/
public static String getUsername() {
Jwt jwt = getCurrentJwt();
return jwt != null ? jwt.getClaimAsString("preferred_username") : null;
}

/**
* Get the user's first name
* @return First name or null if not available
*/
public static String getFirstName() {
Jwt jwt = getCurrentJwt();
return jwt != null ? jwt.getClaimAsString("given_name") : null;
}

/**
* Get the user's last name
* @return Last name or null if not available
*/
public static String getLastName() {
Jwt jwt = getCurrentJwt();
return jwt != null ? jwt.getClaimAsString("family_name") : null;
}

/**
* Get all roles/groups of the user from multiple sources.
* @return List of all unique roles or empty list if not available
*/
public static List<String> getRoles() {
Jwt jwt = getCurrentJwt();
if (jwt == null) {
return List.of();
}

List<String> allRoles = new ArrayList<>();

// combine all group fields
// groups
List<String> groups = jwt.getClaimAsStringList("groups");
if (groups != null) {
allRoles.addAll(groups);
}

// realm_access.roles
try {
Map<String, Object> realmAccess = jwt.getClaim("realm_access");
if (realmAccess != null && realmAccess.get("roles") instanceof List) {
@SuppressWarnings("unchecked")
List<String> realmRoles = (List<String>) realmAccess.get("roles");
if (realmRoles != null) {
allRoles.addAll(realmRoles);
}
}
} catch (Exception e) {
// Ignore parsing errors
}

// resource_access.account.roles
try {
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
if (resourceAccess != null && resourceAccess.get("account") instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> accountAccess = (Map<String, Object>) resourceAccess.get("account");
if (accountAccess != null && accountAccess.get("roles") instanceof List) {
@SuppressWarnings("unchecked")
List<String> accountRoles = (List<String>) accountAccess.get("roles");
if (accountRoles != null) {
allRoles.addAll(accountRoles);
}
}
}
} catch (Exception e) {
}

// remove duplicates
return allRoles.stream().distinct().toList();
}

/**
* Check if the user has a specific role (case-insensitive).
* Searches in groups, realm_access.roles, and resource_access.account.roles
*
* @param role The role to check
* @return true if user has the role, false otherwise
*/
public static boolean hasRole(String role) {
if (role == null) {
return false;
}

List<String> roles = getRoles();
return roles.stream()
.anyMatch(r -> r.equalsIgnoreCase(role));
}


/**
* Get a custom claim from the JWT
* @param claimName Name of the claim
* @return Claim value or null if not available
*/
public static Object getClaim(String claimName) {
Jwt jwt = getCurrentJwt();
return jwt != null ? jwt.getClaim(claimName) : null;
}

/**
* Get a custom claim as String
* @param claimName Name of the claim
* @return Claim value as String or null if not available
*/
public static String getClaimAsString(String claimName) {
Jwt jwt = getCurrentJwt();
return jwt != null ? jwt.getClaimAsString(claimName) : null;
}

/**
* Check if a user is currently authenticated
* @return true if authenticated, false otherwise
*/
public static boolean isAuthenticated() {
return getCurrentJwt() != null;
}
}
215 changes: 215 additions & 0 deletions src/test/java/com/ase/userservice/security/UserInformationJWTTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package com.ase.userservice.security;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

/**
* Tests for UserInformationJWT helper class
*/
class UserInformationJWTTest {

private Jwt testJwt;

@BeforeEach
void setUp() {
// Create a mock JWT with test data matching the real Keycloak token structure
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "0b540a6e-988d-484a-9247-9e3a2f237438");
claims.put("preferred_username", "david");
claims.put("email", "dave@fave.com");
claims.put("given_name", "david");
claims.put("family_name", "daivd");
claims.put("name", "david daivd");

// Groups claim
claims.put("groups", Arrays.asList(
"default-roles-sau",
"manage-users",
"offline_access",
"lecturer",
"uma_authorization"
));

// realm_access with roles
Map<String, Object> realmAccess = new HashMap<>();
realmAccess.put("roles", Arrays.asList(
"default-roles-sau",
"manage-users",
"offline_access",
"lecturer",
"uma_authorization"
));
claims.put("realm_access", realmAccess);

// resource_access with account roles
Map<String, Object> accountAccess = new HashMap<>();
accountAccess.put("roles", Arrays.asList(
"manage-account",
"manage-account-links",
"view-profile"
));
Map<String, Object> resourceAccess = new HashMap<>();
resourceAccess.put("account", accountAccess);
claims.put("resource_access", resourceAccess);

Map<String, Object> headers = new HashMap<>();
headers.put("alg", "RS256");

testJwt = new Jwt(
"test-token-value",
Instant.now(),
Instant.now().plusSeconds(3600),
headers,
claims
);

// Set the JWT in the security context
JwtAuthenticationToken auth = new JwtAuthenticationToken(testJwt);
SecurityContextHolder.getContext().setAuthentication(auth);
}

@AfterEach
void tearDown() {
// Clear security context after each test
SecurityContextHolder.clearContext();
}

@Test
void testGetUserId() {
String userId = UserInformationJWT.getUserId();
assertEquals("0b540a6e-988d-484a-9247-9e3a2f237438", userId);
}

@Test
void testGetUsername() {
String username = UserInformationJWT.getUsername();
assertEquals("david", username);
}

@Test
void testGetEmail() {
String email = UserInformationJWT.getEmail();
assertEquals("dave@fave.com", email);
}

@Test
void testGetFirstName() {
String firstName = UserInformationJWT.getFirstName();
assertEquals("david", firstName);
}

@Test
void testGetLastName() {
String lastName = UserInformationJWT.getLastName();
assertEquals("daivd", lastName);
}

@Test
void testGetRoles() {
List<String> roles = UserInformationJWT.getRoles();

// Should contain roles from groups
assertTrue(roles.contains("lecturer"));
assertTrue(roles.contains("manage-users"));

// Should contain roles from resource_access.account.roles
assertTrue(roles.contains("manage-account"));
assertTrue(roles.contains("view-profile"));

// Should have at least 8 unique roles (5 from groups + 3 from account)
assertTrue(roles.size() >= 8);
}

@Test
void testHasRoleFromGroups() {
assertTrue(UserInformationJWT.hasRole("lecturer"));
assertTrue(UserInformationJWT.hasRole("manage-users"));
}

@Test
void testHasRoleFromRealmAccess() {
assertTrue(UserInformationJWT.hasRole("default-roles-sau"));
assertTrue(UserInformationJWT.hasRole("offline_access"));
}

@Test
void testHasRoleFromResourceAccess() {
assertTrue(UserInformationJWT.hasRole("manage-account"));
assertTrue(UserInformationJWT.hasRole("view-profile"));
assertTrue(UserInformationJWT.hasRole("manage-account-links"));
}

@Test
void testHasRoleCaseInsensitive() {
assertTrue(UserInformationJWT.hasRole("LECTURER"));
assertTrue(UserInformationJWT.hasRole("Manage-Account"));
assertTrue(UserInformationJWT.hasRole("lecturer"));
}

@Test
void testHasRoleNotExists() {
assertFalse(UserInformationJWT.hasRole("admin"));
assertFalse(UserInformationJWT.hasRole("superuser"));
}

@Test
void testHasRoleWithNull() {
assertFalse(UserInformationJWT.hasRole(null));
}

@Test
void testGetClaim() {
Object email = UserInformationJWT.getClaim("email");
assertEquals("dave@fave.com", email);
}

@Test
void testGetClaimAsString() {
String email = UserInformationJWT.getClaimAsString("email");
assertEquals("dave@fave.com", email);
}

@Test
void testIsAuthenticated() {
assertTrue(UserInformationJWT.isAuthenticated());
}

@Test
void testIsAuthenticatedWithoutContext() {
SecurityContextHolder.clearContext();
assertFalse(UserInformationJWT.isAuthenticated());
}

@Test
void testGetUserIdWithoutAuthentication() {
SecurityContextHolder.clearContext();
assertNull(UserInformationJWT.getUserId());
}

@Test
void testGetRolesWithoutAuthentication() {
SecurityContextHolder.clearContext();
List<String> roles = UserInformationJWT.getRoles();
assertTrue(roles.isEmpty());
}

@Test
void testGetRolesNoDuplicates() {
List<String> roles = UserInformationJWT.getRoles();
// Check that there are no duplicates
long uniqueCount = roles.stream().distinct().count();
assertEquals(roles.size(), uniqueCount, "Roles list should not contain duplicates");
}
}
Loading