Skip to content

Commit

Permalink
4.8.19 dev (#17)
Browse files Browse the repository at this point in the history
* Added user authentication and user validations in update and delete API

* Added the code for report API and deleteReported comment APIs

* Removed value of the accesstokenKeyBasePath

* Removed value of the sso keys

---------

Co-authored-by: ruksanasemeir <ruksana.semeir@tarento.com>
Co-authored-by: Sharath Prasad <sharaths.kashyap@gmail.com>
  • Loading branch information
3 people authored Nov 4, 2024
1 parent faeba9e commit 8ec453e
Show file tree
Hide file tree
Showing 12 changed files with 1,179 additions and 10 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@
<artifactId>metrics-core</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
<version>18.0.0</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.tarento.commenthub.authentication.model;

import java.security.PublicKey;

public class KeyData {

private String keyId;
private PublicKey publicKey;

public KeyData(String keyId, PublicKey publicKey) {
this.keyId = keyId;
this.publicKey = publicKey;
}

public String getKeyId() {
return keyId;
}

public void setKeyId(String keyId) {
this.keyId = keyId;
}

public PublicKey getPublicKey() {
return publicKey;
}

public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package com.tarento.commenthub.authentication.util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tarento.commenthub.constant.Constants;
import com.tarento.commenthub.transactional.utils.PropertiesCache;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.keycloak.common.util.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AccessTokenValidator {

@Autowired
KeyManager keyManager;

private static Logger logger = LoggerFactory.getLogger(AccessTokenValidator.class.getName());
private static final ObjectMapper mapper = new ObjectMapper();
private static PropertiesCache cache = PropertiesCache.getInstance();
private static final String REALM_URL =
cache.getProperty(Constants.SSO_URL) + "realms/" + cache.getProperty(Constants.SSO_REALM);


/**
* Validates the provided JWT token.
*
* @param token The JWT token to be validated.
* @return A map containing the token body if the token is valid and not expired, otherwise an
* empty map.
*/
private Map<String, Object> validateToken(String token) {
try {
// Split the token into its elements
String[] tokenElements = token.split("\\.");
// Check if the token has at least three elements
if (tokenElements.length < 3) {
throw new IllegalArgumentException("Invalid token format");
}
// Extract header, body, and signature from token elements
String header = tokenElements[0];
String body = tokenElements[1];
String signature = tokenElements[2];
// Concatenate header and body to form the payload
String payload = header + Constants.DOT_SEPARATOR + body;
// Parse header data from base64 encoded header
Map<String, Object> headerData = mapper.readValue(new String(decodeFromBase64(header)),
new TypeReference<Map<String, Object>>() {
});
String keyId = headerData.get("kid").toString();
// Verify the token signature
boolean isValid = CryptoUtil.verifyRSASign(payload, decodeFromBase64(signature),
keyManager.getPublicKey(keyId).getPublicKey(), Constants.SHA_256_WITH_RSA);
// If token signature is valid, parse token body and check expiration
if (isValid) {
Map<String, Object> tokenBody = mapper.readValue(new String(decodeFromBase64(body)),
new TypeReference<Map<String, Object>>() {
});
if (isExpired((Integer) tokenBody.get("exp"))) {
logger.error("Token expired: {}", token);
return Collections.emptyMap();
}
return tokenBody;
}
} catch (IOException | IllegalArgumentException e) {
logger.error("Error validating token: {}", e.getMessage());
} catch (Exception ex) {
logger.error("Unexpected error validating token: {}", ex.getMessage());
}
return Collections.emptyMap();
}


/**
* Verifies the user token and extracts the user ID from it.
*
* @param token The user token to be verified.
* @return The user ID extracted from the token, or UNAUTHORIZED if verification fails or an
* exception occurs.
*/
public String verifyUserToken(String token) {
// Initialize user ID to UNAUTHORIZED
String userId = Constants.UNAUTHORIZED_USER;
try {
// Validate the token and obtain its payload
Map<String, Object> payload = validateToken(token);
// Check if payload is not empty and issuer is valid
if (!payload.isEmpty() && checkIss((String) payload.get("iss"))) {
// Extract user ID from payload
userId = (String) payload.get(Constants.SUB);
// If user ID is not blank, extract the actual user ID
if (StringUtils.isNotBlank(userId)) {
userId = userId.substring(userId.lastIndexOf(":") + 1);
}
}
} catch (Exception ex) {
logger.error("Exception in verifyUserAccessToken: verify ", ex);
}
return userId;
}

/**
* Checks if the issuer of the token matches the predefined realm URL.
*
* @param iss The issuer extracted from the token.
* @return true if the issuer matches the realm URL, false otherwise.
*/
private boolean checkIss(String iss) {
// Check if the realm URL is blank or if the issuer does not match the realm URL
if (StringUtils.isBlank(REALM_URL) || !REALM_URL.equalsIgnoreCase(iss)) {
logger.warn("Issuer does not match the expected realm URL. Issuer: {}, Expected: {}", iss,
REALM_URL);
return false;
}
logger.info("Issuer validation successful. Issuer: {}", iss);
return true;
}


private boolean isExpired(Integer expiration) {
return (Time.currentTime() > expiration);
}

private byte[] decodeFromBase64(String data) {
return Base64Util.decode(data, 11);
}

/**
* Fetches the user ID from the provided access token.
*
* @param accessToken The access token from which to fetch the user ID.
* @return The user ID fetched from the access token, or null if the token is invalid or an
* exception occurs.
*/
public String fetchUserIdFromAccessToken(String accessToken) {
// Initialize clientAccessTokenId to null
String clientAccessTokenId = null;
// Check if the accessToken is not null
if (accessToken != null) {
try {
// Verify the access token to fetch the user ID
clientAccessTokenId = verifyUserToken(accessToken);
// If the user ID is UNAUTHORIZED, set it to null
if (Constants.UNAUTHORIZED_USER.equalsIgnoreCase(clientAccessTokenId)) {
clientAccessTokenId = null;
}
} catch (Exception ex) {
String errMsg =
"Exception occurred while fetching the userid from the access token. Exception: "
+ ex.getMessage();
logger.error(errMsg, ex);
clientAccessTokenId = null;
}
}
return clientAccessTokenId;
}

}
Loading

0 comments on commit 8ec453e

Please sign in to comment.