Skip to content

Conversation

@ZiyamSanthosh
Copy link
Contributor

@ZiyamSanthosh ZiyamSanthosh commented Nov 25, 2025

This PR introduces IdentityContextUtil class to have util methods related to IdentityContext. Currently, it has been added with methods to resolve client IP address and User Agent from the request retrieved from the IdentityContext.

Also, the existing Request object is introduced with a first class ipAddress attribute to preserve remoteAddress value from the Servlet request object.

Related PR:

Summary by CodeRabbit

  • New Features
    • Request objects now capture client IP addresses.
    • Added support for User-Agent and X-Forwarded-User-Agent headers.
    • New utilities to extract client IP and User-Agent from request/context, preferring forwarded headers when present.
    • Improved handling and logging when multiple header values are present for IP/User-Agent.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings November 25, 2025 12:33
Comment on lines 62 to 66

if (request != null) {
resolveHeaders(request);
this.ipAddress = request.getRemoteAddr();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log Improvement Suggestion No: 1

Suggested change
if (request != null) {
resolveHeaders(request);
this.ipAddress = request.getRemoteAddr();
}
if (request != null) {
resolveHeaders(request);
this.ipAddress = request.getRemoteAddr();
}
log.debug("Request builder initialized with IP address: {}", this.ipAddress);

Comment on lines 46 to 50
*/
public static String getClientIpAddress(IdentityContext identityContext) {

Request request = getRequestFromContext(identityContext);
return request != null ? getClientIpAddress(request) : null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log Improvement Suggestion No: 2

Suggested change
*/
public static String getClientIpAddress(IdentityContext identityContext) {
Request request = getRequestFromContext(identityContext);
return request != null ? getClientIpAddress(request) : null;
public static String getClientIpAddress(IdentityContext identityContext) {
Request request = getRequestFromContext(identityContext);
if (request != null) {
LOG.debug("Resolving client IP address from IdentityContext.");
return getClientIpAddress(request);
}
LOG.debug("Request is null in IdentityContext. Cannot resolve client IP address.");
return null;

Comment on lines 70 to 73
}
String ip = values.get(0);
if (StringUtils.isNotEmpty(ip) && !IdentityConstants.UNKNOWN.equalsIgnoreCase(ip)) {
return IdentityUtil.getFirstIP(ip);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log Improvement Suggestion No: 3

Suggested change
}
String ip = values.get(0);
if (StringUtils.isNotEmpty(ip) && !IdentityConstants.UNKNOWN.equalsIgnoreCase(ip)) {
return IdentityUtil.getFirstIP(ip);
String ip = values.get(0);
if (StringUtils.isNotEmpty(ip) && !IdentityConstants.UNKNOWN.equalsIgnoreCase(ip)) {
String resolvedIp = IdentityUtil.getFirstIP(ip);
if (LOG.isDebugEnabled()) {
LOG.debug("Client IP address resolved from header " + ipHeader + ": " + resolvedIp);
}
return resolvedIp;

Copy link
Contributor

@wso2-engineering wso2-engineering bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Agent Log Improvement Checklist

⚠️ Warning: AI-Generated Review Comments

  • The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
  • Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.

✅ Before merging this pull request:

  • Review all AI-generated comments for accuracy and relevance.
  • Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
Comment Accepted (Y/N) Reason
#### Log Improvement Suggestion No: 1
#### Log Improvement Suggestion No: 2
#### Log Improvement Suggestion No: 3

@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds two User-Agent header constants, extends Request with an ipAddress field populated from HttpServletRequest.getRemoteAddr(), and introduces IdentityContextUtil with static helpers to resolve client IP and User-Agent from IdentityContext/Request headers with fallbacks and logging.

Changes

Cohort / File(s) Summary
Constants Addition
components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java
Added public constants USER_AGENT = "User-Agent" and X_FORWARDED_USER_AGENT = "X-Forwarded-User-Agent".
Request Model Enhancement
components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java
Added private final String ipAddress, getter getIpAddress(), Builder ipAddress field, assignment in Request(Builder), and Builder.fromHttpRequest() sets ipAddress from HttpServletRequest.getRemoteAddr().
Context Utility
components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java
New utility class with getClientIpAddress() and getClientUserAgent() methods that read headers (including X-Forwarded-* and User-Agent), choose the first valid value (using IdentityUtil.getFirstIP for IPs), log multiple values, and fallback to Request.ipAddress or null.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Caller
    participant IC as IdentityContext (thread-local)
    participant Util as IdentityContextUtil
    participant Req as Request
    participant H as Request.headers

    Caller->>Util: getClientIpAddress()
    Util->>IC: read thread-local IdentityContext
    IC-->>Util: Request
    Util->>Req: access headers map and ipAddress
    Req->>H: provide header entries
    H-->>Util: X-Forwarded-For / other IP headers
    alt header contains valid IP
        Util-->>Caller: first valid IP (from headers)
    else no valid header
        Util-->>Caller: Request.ipAddress (remoteAddr) or null
    end

    par user-agent resolution
        Caller->>Util: getClientUserAgent()
        Util->>Req: read headers
        H-->>Util: X-Forwarded-User-Agent and/or User-Agent
        alt X-Forwarded-User-Agent present
            Util-->>Caller: first X-Forwarded-User-Agent value
        else if User-Agent present
            Util-->>Caller: first User-Agent value
        else
            Util-->>Caller: null
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Check Request.Builder.fromHttpRequest for null-safety and correct remote-addr capture.
  • Verify IdentityContextUtil header precedence, parsing of comma-separated header values, and filtering (e.g., "unknown").
  • Confirm new constants are referenced consistently and logging levels/messages are appropriate.

Poem

🐇 I hopped through headers, soft and fleet,

Found IP footprints and agents neat,
Two new constants snug in place,
Request keeps address, no race,
Utilities hum — metadata complete.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the core changes but is missing several required template sections including Purpose/Goals, Approach, User stories, Release note, Documentation, and others. Complete the PR description by filling out required sections from the template: Purpose, Goals, Approach, User stories, Release note, Documentation, Training, Certification, Marketing, Automation tests, Security checks, and Test environment.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding IdentityContextUtil methods to resolve client IP and User Agent from the Request object.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63df921 and cf0ed03.

📒 Files selected for processing (3)
  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java (1 hunks)
  • components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java (1 hunks)
  • components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java
  • components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{py,java,ts,tsx,js,jsx,cs,go,rb,php}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All public methods should have a docstring

Files:

  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java
**/*.{py,java,ts,tsx,js,jsx,cs,go,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{py,java,ts,tsx,js,jsx,cs,go,rb,php,c,cpp,h,hpp}: Comments should start with a space and first letter capitalized
Comments should always end with a period

Files:

  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.java: If there's a string concatenation in a debug log, then having if (LOG.isDebugEnabled()) is mandatory to avoid unnecessary computation
For simple log messages (e.g., static strings or simple variable interpolation), you can use LOG.debug directly without the debug check

Files:

  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java
**/*.{java,ts,tsx,js,jsx,py,cs,go,php}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{java,ts,tsx,js,jsx,py,cs,go,php}: Scrutinize all user-controlled input for potential SQL Injection, Cross-Site Scripting (XSS), or Command Injection
Ensure that no sensitive user data (e.g., PII, credentials) is being logged or sent in error messages

Files:

  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java
**/*.{java,ts,tsx,js,jsx,py,cs,go,php,yml,yaml,json,env,properties,conf}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Search for and eliminate any exposed secrets like API keys, passwords, or private tokens

Files:

  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java
🧠 Learnings (1)
📚 Learning: 2025-11-11T00:57:09.814Z
Learnt from: Thisara-Welmilla
Repo: wso2/carbon-identity-framework PR: 7603
File: components/external-api-framework/org.wso2.carbon.identity.external.api.client/src/main/java/org/wso2/carbon/identity/external/api/client/internal/service/APIClient.java:131-153
Timestamp: 2025-11-11T00:57:09.814Z
Learning: In the file `components/external-api-framework/org.wso2.carbon.identity.external.api.client/src/main/java/org/wso2/carbon/identity/external/api/client/internal/service/APIClient.java`, the `setRequestEntity` method intentionally allows custom headers from `requestContext.getHeaders()` to override default headers including "Authorization", "Accept", and "Content-Type". This is by design to provide maximum flexibility to callers.

Applied to files:

  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java
🔇 Additional comments (1)
components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java (1)

157-160: User-Agent header constants look correct and consistent

The comment style and the new USER_AGENT and X_FORWARDED_USER_AGENT constants are clear, follow existing patterns in this class, and introduce no behavioral or security concerns.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot finished reviewing on behalf of ZiyamSanthosh November 25, 2025 12:36
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java (1)

35-41: ipAddress field wiring looks correct; consider documenting nullability

ipAddress is correctly wired from the builder and exposed via getIpAddress(). Since it can legitimately be null when the builder is not initialized from an HttpServletRequest, it would be helpful to document that in the Javadoc of Request or getIpAddress() so callers don’t assume it’s always non-null.

components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java (2)

59-80: Guard against null Request in public utility methods

Both getClientIpAddress(Request request) and getClientUserAgent(Request request) assume request is non-null and will throw NullPointerException if called defensively with a null value. Since these are public utilities, it’s safer either to:

  • Explicitly guard against null and return null (with a debug log), or
  • Enforce non-null via an early Objects.requireNonNull(request, "...") and document it.

Example (if you prefer graceful handling):

-    public static String getClientIpAddress(Request request) {
-
-        Map<String, List<String>> headerMap = request.getHeaders().stream()
+    public static String getClientIpAddress(Request request) {
+        if (request == null) {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Request is null. Cannot resolve client IP address.");
+            }
+            return null;
+        }
+
+        Map<String, List<String>> headerMap = request.getHeaders().stream()
                 .collect(Collectors.toMap(Header::getName, Header::getValue));

Apply the same pattern (or a documented non-null contract) for getClientUserAgent(Request request) for consistency.

Also applies to: 100-123


59-76: Header lookup is case-sensitive; consider normalizing for robustness

headerMap keys are taken directly from Header::getName and then looked up using constants like "X-Forwarded-For" and "User-Agent". If a container or intermediary normalizes header names to a different case (e.g., x-forwarded-for), these lookups will fail.

Consider normalizing header names (e.g., to lower case) both when building the map and in the constants you use for lookup, to make the resolution logic case-insensitive. This is an optional hardening step but can avoid subtle interoperability issues.

Also applies to: 102-107

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66b9294 and 2c719bf.

📒 Files selected for processing (3)
  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java (1 hunks)
  • components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java (1 hunks)
  • components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java (1 hunks)
🔇 Additional comments (3)
components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java (1)

157-160: User-Agent header constants are appropriate and consistent

The added USER_AGENT and X_FORWARDED_USER_AGENT constants match standard header names and integrate cleanly with the new utility methods.

components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java (2)

48-51: Accessor behavior aligns with the new field

getIpAddress() is a simple accessor over the captured servlet remoteAddr, which is exactly what the new utility methods expect. No functional issues here.


61-66: fromHttpRequest correctly captures servlet remote address

Populating ipAddress from request.getRemoteAddr() in fromHttpRequest is consistent with the goal of preserving the original servlet remote address and lets higher-level utilities apply their own proxy/header logic. This looks good as-is.

@ZiyamSanthosh ZiyamSanthosh force-pushed the master-add-util-identity-context branch from 2c719bf to 63df921 Compare November 25, 2025 12:40
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces the IdentityContextUtil utility class with methods to extract client IP addresses and User-Agent information from IdentityContext and Request objects. It also enhances the Request model to include a first-class ipAddress attribute populated from HttpServletRequest.getRemoteAddr().

  • Added IdentityContextUtil class with IP and User-Agent resolution methods
  • Enhanced Request class with ipAddress field and getter method
  • Added User-Agent related constants to IdentityConstants

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.

File Description
IdentityContextUtil.java New utility class providing methods to resolve client IP address and User-Agent from IdentityContext and Request objects
Request.java Added ipAddress field to preserve remote address from servlet request
IdentityConstants.java Added User-Agent header constants (USER_AGENT and X_FORWARDED_USER_AGENT)

You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

return Collections.unmodifiableList(headers);
}

public String getIpAddress() {
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing docstring for public method. All public methods should have a docstring. Consider adding:

/**
 * Gets the IP address of the request.
 *
 * @return The IP address.
 */
public String getIpAddress() {

Copilot generated this review using guidance from repository custom instructions.
Comment on lines 61 to 62
Map<String, List<String>> headerMap = request.getHeaders().stream()
.collect(Collectors.toMap(Header::getName, Header::getValue));
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stream collection to a map may throw IllegalStateException if there are duplicate header names with different cases (e.g., "User-Agent" and "user-agent"). HTTP headers are case-insensitive, but the current code treats them as case-sensitive keys. Consider using Collectors.toMap() with a merge function to handle duplicates, or use a case-insensitive map structure like TreeMap with String.CASE_INSENSITIVE_ORDER.

Copilot uses AI. Check for mistakes.
Comment on lines 102 to 103
Map<String, List<String>> headerMap = request.getHeaders().stream()
.collect(Collectors.toMap(Header::getName, Header::getValue));
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stream collection to a map may throw IllegalStateException if there are duplicate header names with different cases (e.g., "User-Agent" and "user-agent"). HTTP headers are case-insensitive, but the current code treats them as case-sensitive keys. Consider using Collectors.toMap() with a merge function to handle duplicates, or use a case-insensitive map structure like TreeMap with String.CASE_INSENSITIVE_ORDER.

Copilot uses AI. Check for mistakes.
Comment on lines 100 to 118
public static String getClientUserAgent(Request request) {

Map<String, List<String>> headerMap = request.getHeaders().stream()
.collect(Collectors.toMap(Header::getName, Header::getValue));

List<String> userAgent = headerMap.get(IdentityConstants.USER_AGENT);
List<String> forwardedUserAgent = headerMap.get(IdentityConstants.X_FORWARDED_USER_AGENT);
if (forwardedUserAgent != null && !forwardedUserAgent.isEmpty()) {
if (LOG.isDebugEnabled() && forwardedUserAgent.size() > 1) {
LOG.debug("Multiple values found for header: " + IdentityConstants.X_FORWARDED_USER_AGENT +
". Using the first value as the User-Agent.");
}
return forwardedUserAgent.get(0);
} else if (userAgent != null && !userAgent.isEmpty()) {
if (LOG.isDebugEnabled() && userAgent.size() > 1) {
LOG.debug("Multiple values found for header: " + IdentityConstants.USER_AGENT +
". Using the first value as the User-Agent.");
}
return userAgent.get(0);
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a map from the headers stream on every call is inefficient. If getClientUserAgent is called frequently, consider either:

  1. Caching the header map in the Request object
  2. Iterating through the headers list directly instead of creating an intermediate map
  3. Adding a method to Request class to retrieve headers by name

For example:

for (Header header : request.getHeaders()) {
    String name = header.getName();
    if (IdentityConstants.X_FORWARDED_USER_AGENT.equalsIgnoreCase(name)) {
        return header.getValue().isEmpty() ? null : header.getValue().get(0);
    }
}
// ... continue with USER_AGENT check
Suggested change
public static String getClientUserAgent(Request request) {
Map<String, List<String>> headerMap = request.getHeaders().stream()
.collect(Collectors.toMap(Header::getName, Header::getValue));
List<String> userAgent = headerMap.get(IdentityConstants.USER_AGENT);
List<String> forwardedUserAgent = headerMap.get(IdentityConstants.X_FORWARDED_USER_AGENT);
if (forwardedUserAgent != null && !forwardedUserAgent.isEmpty()) {
if (LOG.isDebugEnabled() && forwardedUserAgent.size() > 1) {
LOG.debug("Multiple values found for header: " + IdentityConstants.X_FORWARDED_USER_AGENT +
". Using the first value as the User-Agent.");
}
return forwardedUserAgent.get(0);
} else if (userAgent != null && !userAgent.isEmpty()) {
if (LOG.isDebugEnabled() && userAgent.size() > 1) {
LOG.debug("Multiple values found for header: " + IdentityConstants.USER_AGENT +
". Using the first value as the User-Agent.");
}
return userAgent.get(0);
/**
* Resolves the client User-Agent from the given Request.
*
* @param request The Request object.
* @return The client User-Agent if found, else null.
*/
public static String getClientUserAgent(Request request) {
// Iterate through headers to find X_FORWARDED_USER_AGENT or USER_AGENT.
List<Header> headers = request.getHeaders();
for (Header header : headers) {
String name = header.getName();
List<String> values = header.getValue();
if (IdentityConstants.X_FORWARDED_USER_AGENT.equalsIgnoreCase(name)) {
if (values != null && !values.isEmpty()) {
if (LOG.isDebugEnabled() && values.size() > 1) {
LOG.debug("Multiple values found for header: " + IdentityConstants.X_FORWARDED_USER_AGENT +
". Using the first value as the User-Agent.");
}
return values.get(0);
}
}
}
for (Header header : headers) {
String name = header.getName();
List<String> values = header.getValue();
if (IdentityConstants.USER_AGENT.equalsIgnoreCase(name)) {
if (values != null && !values.isEmpty()) {
if (LOG.isDebugEnabled() && values.size() > 1) {
LOG.debug("Multiple values found for header: " + IdentityConstants.USER_AGENT +
". Using the first value as the User-Agent.");
}
return values.get(0);
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +114
if (LOG.isDebugEnabled() && forwardedUserAgent.size() > 1) {
LOG.debug("Multiple values found for header: " + IdentityConstants.X_FORWARDED_USER_AGENT +
". Using the first value as the User-Agent.");
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug logging should be wrapped in an if (LOG.isDebugEnabled()) check when performing string concatenation. The current condition if (LOG.isDebugEnabled() && forwardedUserAgent.size() > 1) still performs the string concatenation in the LOG.debug() call even when the size check fails but debug is enabled. Consider restructuring:

if (LOG.isDebugEnabled()) {
    if (forwardedUserAgent.size() > 1) {
        LOG.debug("Multiple values found for header: " + IdentityConstants.X_FORWARDED_USER_AGENT +
                ". Using the first value as the User-Agent.");
    }
}

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +114 to +120
if (LOG.isDebugEnabled() && userAgent.size() > 1) {
LOG.debug("Multiple values found for header: " + IdentityConstants.USER_AGENT +
". Using the first value as the User-Agent.");
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug logging should be wrapped in an if (LOG.isDebugEnabled()) check when performing string concatenation. The current condition if (LOG.isDebugEnabled() && userAgent.size() > 1) still performs the string concatenation in the LOG.debug() call even when the size check fails but debug is enabled. Consider restructuring:

if (LOG.isDebugEnabled()) {
    if (userAgent.size() > 1) {
        LOG.debug("Multiple values found for header: " + IdentityConstants.USER_AGENT +
                ". Using the first value as the User-Agent.");
    }
}

Copilot generated this review using guidance from repository custom instructions.
Comment on lines 67 to 69
if (LOG.isDebugEnabled() && values.size() > 1) {
LOG.debug("Multiple values found for header: " + ipHeader +
". Using the first value as the client IP address.");
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug logging should be wrapped in an if (LOG.isDebugEnabled()) check when performing string concatenation. The current condition if (LOG.isDebugEnabled() && values.size() > 1) still performs the string concatenation in the LOG.debug() call even when the size check fails but debug is enabled. Consider restructuring:

if (LOG.isDebugEnabled()) {
    if (values.size() > 1) {
        LOG.debug("Multiple values found for header: " + ipHeader +
                ". Using the first value as the client IP address.");
    }
}

Copilot generated this review using guidance from repository custom instructions.
Comment on lines 64 to 75
for (String ipHeader : IdentityConstants.HEADERS_WITH_IP) {
List<String> values = headerMap.get(ipHeader);
if (values != null && !values.isEmpty()) {
if (LOG.isDebugEnabled() && values.size() > 1) {
LOG.debug("Multiple values found for header: " + ipHeader +
". Using the first value as the client IP address.");
}
String ip = values.get(0);
if (StringUtils.isNotEmpty(ip) && !IdentityConstants.UNKNOWN.equalsIgnoreCase(ip)) {
return IdentityUtil.getFirstIP(ip);
}
}
}
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vulnerability: IP Spoofing via Header Manipulation

Risk: The code trusts IP addresses from HTTP headers (X-Forwarded-For, Proxy-Client-IP, etc.) which can be easily spoofed by malicious clients. This could lead to incorrect IP-based authentication, authorization bypass, or audit log tampering.

Suggestion: Ensure that:

  1. This method is only used behind a trusted reverse proxy that properly sanitizes these headers
  2. Add documentation warning about the security implications
  3. Consider validating IP format and rejecting obviously invalid IPs
  4. If used for security-sensitive operations (authentication, rate limiting, audit), add additional verification mechanisms

Copilot uses AI. Check for mistakes.
Comment on lines 61 to 74
Map<String, List<String>> headerMap = request.getHeaders().stream()
.collect(Collectors.toMap(Header::getName, Header::getValue));

for (String ipHeader : IdentityConstants.HEADERS_WITH_IP) {
List<String> values = headerMap.get(ipHeader);
if (values != null && !values.isEmpty()) {
if (LOG.isDebugEnabled() && values.size() > 1) {
LOG.debug("Multiple values found for header: " + ipHeader +
". Using the first value as the client IP address.");
}
String ip = values.get(0);
if (StringUtils.isNotEmpty(ip) && !IdentityConstants.UNKNOWN.equalsIgnoreCase(ip)) {
return IdentityUtil.getFirstIP(ip);
}
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a map from the headers stream on every call is inefficient. If getClientIpAddress is called frequently, consider either:

  1. Caching the header map in the Request object
  2. Iterating through the headers list directly instead of creating an intermediate map
  3. Adding a method to Request class to retrieve headers by name

For example:

for (String ipHeader : IdentityConstants.HEADERS_WITH_IP) {
    for (Header header : request.getHeaders()) {
        if (ipHeader.equalsIgnoreCase(header.getName())) {
            List<String> values = header.getValue();
            // ... rest of logic
        }
    }
}
Suggested change
Map<String, List<String>> headerMap = request.getHeaders().stream()
.collect(Collectors.toMap(Header::getName, Header::getValue));
for (String ipHeader : IdentityConstants.HEADERS_WITH_IP) {
List<String> values = headerMap.get(ipHeader);
if (values != null && !values.isEmpty()) {
if (LOG.isDebugEnabled() && values.size() > 1) {
LOG.debug("Multiple values found for header: " + ipHeader +
". Using the first value as the client IP address.");
}
String ip = values.get(0);
if (StringUtils.isNotEmpty(ip) && !IdentityConstants.UNKNOWN.equalsIgnoreCase(ip)) {
return IdentityUtil.getFirstIP(ip);
}
// Iterate through the headers list directly to avoid creating an intermediate map.
for (String ipHeader : IdentityConstants.HEADERS_WITH_IP) {
for (Header header : request.getHeaders()) {
if (ipHeader.equalsIgnoreCase(header.getName())) {
List<String> values = header.getValue();
if (values != null && !values.isEmpty()) {
if (LOG.isDebugEnabled() && values.size() > 1) {
LOG.debug("Multiple values found for header: " + ipHeader +
". Using the first value as the client IP address.");
}
String ip = values.get(0);
if (StringUtils.isNotEmpty(ip) && !IdentityConstants.UNKNOWN.equalsIgnoreCase(ip)) {
return IdentityUtil.getFirstIP(ip);
}
}
}

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java (1)

48-51: Document that this getter can return null.

Since HttpServletRequest.getRemoteAddr() may return null, and the ipAddress field is nullable, consider adding javadoc to document this behavior for callers.

Apply this diff to add documentation:

+    /**
+     * Gets the IP address of the client.
+     *
+     * @return the client IP address, or null if not available
+     */
     public String getIpAddress() {
 
         return ipAddress;
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c719bf and 63df921.

📒 Files selected for processing (3)
  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java (1 hunks)
  • components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java (1 hunks)
  • components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/identity-core/org.wso2.carbon.identity.base/src/main/java/org/wso2/carbon/identity/base/IdentityConstants.java
  • components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java
🔇 Additional comments (1)
components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java (1)

35-35: LGTM: IP address field properly implemented.

The ipAddress field follows the correct immutability pattern—final in the Request class and mutable in the Builder. The constructor properly copies the value from the builder.

Also applies to: 40-40, 59-59


if (request != null) {
resolveHeaders(request);
this.ipAddress = request.getRemoteAddr();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify null handling in IdentityContextUtil.

The request.getRemoteAddr() call can return null according to the Servlet API specification. Ensure that IdentityContextUtil properly handles the case where ipAddress is null.

Run the following script to verify how IdentityContextUtil handles null IP addresses:


🏁 Script executed:

#!/bin/bash
# Description: Verify that IdentityContextUtil handles null ipAddress properly

# Search for getClientIpAddress implementations and check null handling
ast-grep --pattern $'getClientIpAddress($$$) {
  $$$
}'

# Also check for direct usage of Request.getIpAddress()
rg -nP -A 5 'getIpAddress\(\)' --type=java

Length of output: 1824


🏁 Script executed:

#!/bin/bash
# Get full context around IdentityContextUtil line 79 and find getClientIpAddress method

echo "=== Context around line 79 in IdentityContextUtil ==="
rg -nP -B 10 -A 10 'request\.getIpAddress\(\)' \
  components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java

echo -e "\n=== Looking for getClientIpAddress method ==="
rg -nP 'getClientIpAddress|ClientIpAddress' \
  components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java

echo -e "\n=== Full IdentityContextUtil file to understand structure ==="
cat components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/util/IdentityContextUtil.java

Length of output: 6846


Handle potential null return from IP address fallback path.

Verification confirms that request.getRemoteAddr() can return null per the Servlet API specification. When IdentityContextUtil.getClientIpAddress(Request) falls back to request.getIpAddress() (line 79), this can return null without explicit handling or documentation. Consider either:

  • Documenting that the method can return null and require callers to handle it, or
  • Providing a fallback/default value when both header-based resolution and getRemoteAddr() return null
🤖 Prompt for AI Agents
In
components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/model/Request.java
around line 65, request.getRemoteAddr() can return null; update the assignment
to handle null by providing a safe fallback (e.g., set ipAddress to an explicit
default like "unknown" or empty string) and add a brief Javadoc or inline
comment noting that the field may be a default when no client IP is available so
callers know it can be non-specific.

* @param identityContext The IdentityContext object.
* @return The client IP address if found, else falls back to the request IP address.
*/
public static String getClientIpAddress(IdentityContext identityContext) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to pass the identity context to this method ?
Can't we directly access the identity context inside the method ?


for (String ipHeader : IdentityConstants.HEADERS_WITH_IP) {
List<String> values = headerMap.get(ipHeader);
if (values != null && !values.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (values != null && !values.isEmpty()) {
if (!CollectionUtils.isEmpty(values)) {

* @param request The Request object.
* @return The client IP address if found, else falls back to the request IP address.
*/
public static String getClientIpAddress(Request request) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to make this public ?


List<String> userAgent = headerMap.get(IdentityConstants.USER_AGENT);
List<String> forwardedUserAgent = headerMap.get(IdentityConstants.X_FORWARDED_USER_AGENT);
if (forwardedUserAgent != null && !forwardedUserAgent.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (forwardedUserAgent != null && !forwardedUserAgent.isEmpty()) {
if (!CollectionUtils.isEmpty(forwardedUserAgent)) {

". Using the first value as the User-Agent.");
}
return forwardedUserAgent.get(0);
} else if (userAgent != null && !userAgent.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if (userAgent != null && !userAgent.isEmpty()) {
} else if (!CollectionUtils.isEmpty(userAgent)) {

@codecov
Copy link

codecov bot commented Nov 25, 2025

Codecov Report

❌ Patch coverage is 0% with 53 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.46%. Comparing base (f3a5f91) to head (cf0ed03).
⚠️ Report is 9 commits behind head on master.

Files with missing lines Patch % Lines
...dentity/core/context/util/IdentityContextUtil.java 0.00% 50 Missing ⚠️
...o2/carbon/identity/core/context/model/Request.java 0.00% 3 Missing ⚠️

❌ Your patch check has failed because the patch coverage (0.00%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #7632      +/-   ##
============================================
- Coverage     50.67%   50.46%   -0.22%     
+ Complexity    19876    18939     -937     
============================================
  Files          2121     2122       +1     
  Lines        130029   128055    -1974     
  Branches      27000    26601     -399     
============================================
- Hits          65894    64618    -1276     
+ Misses        55787    55173     -614     
+ Partials       8348     8264      -84     
Flag Coverage Δ
unit 35.59% <0.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ZiyamSanthosh ZiyamSanthosh force-pushed the master-add-util-identity-context branch from 63df921 to cf0ed03 Compare November 25, 2025 13:33
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants