Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
import java.util.HashMap;
import java.util.Map;

import static org.wso2.carbon.apimgt.impl.APIConstants.MCP_HTTP_METHOD;

public class McpInitHandler extends AbstractHandler implements ManagedLifecycle {
private static final Log log = LogFactory.getLog(McpInitHandler.class);

Expand Down Expand Up @@ -159,6 +161,7 @@ private String buildMCPRequest(MessageContext messageContext) throws McpExceptio

method = request.getMethod();
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
method = request.getMethod();
method = request.getMethod();
log.info("MCP request received with method: " + method);

messageContext.setProperty(APIMgtGatewayConstants.MCP_METHOD, method);
messageContext.setProperty(MCP_HTTP_METHOD, method);
messageContext.setProperty(APIMgtGatewayConstants.MCP_REQUEST_BODY, request);

if (StringUtils.equals(method, APIConstants.MCP.METHOD_TOOL_CALL)) {
Expand All @@ -183,10 +186,14 @@ private String buildMCPRequest(MessageContext messageContext) throws McpExceptio
}
}
if (backendOperation != 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
if (backendOperation != null) {
if (backendOperation != null) {
log.debug("Backend operation found for tool: " + toolName + ", HTTP method: " + backendOperation.getVerb() + ", target: " + backendOperation.getTarget());

messageContext.setProperty("MCP_HTTP_METHOD", backendOperation.getVerb());
messageContext.setProperty(MCP_HTTP_METHOD, backendOperation.getVerb());
messageContext.setProperty("MCP_API_ELECTED_RESOURCE", backendOperation.getTarget());
}
}
} else if (StringUtils.equals(method, APIConstants.MCP.METHOD_INITIALIZE)) {
Map<String, String> transportHeaderMap = (Map<String, String>) axis2MC.getProperty
(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
Comment on lines +194 to +195
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

In METHOD_INITIALIZE branch, transportHeaderMap can be null if TRANSPORT_HEADERS property is absent, causing a NullPointerException on remove(...). Please null-check (and initialize a new map + set it back) before removing the session header, or guard the removal when headers are missing.

Suggested change
Map<String, String> transportHeaderMap = (Map<String, String>) axis2MC.getProperty
(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
Map<String, String> transportHeaderMap = (Map<String, String>) axis2MC.getProperty(
org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
if (transportHeaderMap == null) {
transportHeaderMap = new HashMap<>();
axis2MC.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, transportHeaderMap);
}

Copilot uses AI. Check for mistakes.
transportHeaderMap.remove(APIConstants.MCP.HEADER_MCP_SESSION_ID);
}
} else {
throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE,
Expand All @@ -212,14 +219,8 @@ private boolean isNoAuthMCPRequest(String method) throws McpException {

case APIConstants.MCP.METHOD_TOOL_LIST:
case APIConstants.MCP.METHOD_TOOL_CALL:
return false;

default:
throw new McpException(
APIConstants.MCP.RpcConstants.METHOD_NOT_FOUND_CODE,
APIConstants.MCP.RpcConstants.METHOD_NOT_FOUND_MESSAGE,
"Method not found"
);
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,15 @@
import org.wso2.carbon.apimgt.gateway.handlers.security.authenticator.InternalAPIKeyAuthenticator;
import org.wso2.carbon.apimgt.gateway.handlers.security.basicauth.BasicAuthAuthenticator;
import org.wso2.carbon.apimgt.gateway.handlers.security.oauth.OAuthAuthenticator;
import org.wso2.carbon.apimgt.gateway.internal.DataHolder;
import org.wso2.carbon.apimgt.gateway.internal.ServiceReferenceHolder;
import org.wso2.carbon.apimgt.gateway.utils.GatewayUtils;
import org.wso2.carbon.apimgt.gateway.utils.MCPUtils;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.APIManagerConfiguration;
import org.wso2.carbon.apimgt.impl.APIManagerConfigurationService;
import org.wso2.carbon.apimgt.impl.dto.KeyManagerDto;
import org.wso2.carbon.apimgt.impl.factory.KeyManagerHolder;
import org.wso2.carbon.apimgt.impl.utils.APIUtil;
import org.wso2.carbon.apimgt.keymgt.model.entity.API;
import org.wso2.carbon.apimgt.tracing.TracingSpan;
Expand Down Expand Up @@ -475,10 +478,13 @@ public boolean handleRequest(MessageContext messageContext) {
}

if (APIConstants.API_TYPE_MCP.equalsIgnoreCase(apiType) && isMCPNoAuthRequest) {
log.debug("Skipping authentication for MCP request"
+ ", method: " + messageContext.getProperty(APIMgtGatewayConstants.MCP_METHOD));
// TODO: Check if we need to handle same as the no auth case for REST
return true;
if (log.isDebugEnabled()) {
log.debug("Skipping authentication for MCP request"
+ ", method: " + messageContext.getProperty(APIMgtGatewayConstants.MCP_METHOD));
}
handleNoAuthentication(messageContext);
setAPIParametersToMessageContext(messageContext);
return ExtensionListenerUtil.postProcessRequest(messageContext, type);
Comment on lines +485 to +487
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
handleNoAuthentication(messageContext);
setAPIParametersToMessageContext(messageContext);
return ExtensionListenerUtil.postProcessRequest(messageContext, type);
handleNoAuthentication(messageContext);
setAPIParametersToMessageContext(messageContext);
log.info("Authentication skipped for MCP no-auth request");
return ExtensionListenerUtil.postProcessRequest(messageContext, type);

Comment on lines +481 to +487
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

The MCP no-auth short-circuit returns postProcessRequest(...) without invoking ExtensionListenerUtil.preProcessRequest(...), unlike the normal request path. This changes extension listener behavior for MCP no-auth requests; consider calling preProcessRequest (and honoring its return) before handleNoAuthentication(...) to keep the listener lifecycle consistent.

Suggested change
if (log.isDebugEnabled()) {
log.debug("Skipping authentication for MCP request"
+ ", method: " + messageContext.getProperty(APIMgtGatewayConstants.MCP_METHOD));
}
handleNoAuthentication(messageContext);
setAPIParametersToMessageContext(messageContext);
return ExtensionListenerUtil.postProcessRequest(messageContext, type);
if (ExtensionListenerUtil.preProcessRequest(messageContext, type)) {
if (log.isDebugEnabled()) {
log.debug("Skipping authentication for MCP request"
+ ", method: " + messageContext.getProperty(APIMgtGatewayConstants.MCP_METHOD));
}
handleNoAuthentication(messageContext);
setAPIParametersToMessageContext(messageContext);
return ExtensionListenerUtil.postProcessRequest(messageContext, type);
}
return false;

Copilot uses AI. Check for mistakes.
}

if (ExtensionListenerUtil.preProcessRequest(messageContext, type)) {
Expand Down Expand Up @@ -793,23 +799,15 @@ private void handleAuthFailure(MessageContext messageContext, APISecurityExcepti
}
hostHeader = APIUtil.getHostAddress();
}

String gwURL = MCPUtils.getGatewayServerURL(hostHeader, contextPath);
if (StringUtils.isEmpty(gwURL)) {
headers.put(HttpHeaders.WWW_AUTHENTICATE, getAuthenticatorsChallengeString() +
" error=\"invalid_token\"" +
", error_description=\"The provided token is invalid\"");
} else {
if (log.isDebugEnabled()) {
log.debug("Constructed gateway URL for resource metadata: " + gwURL);
}

String resourceMetadata = gwURL + contextPath + APIMgtGatewayConstants.MCP_WELL_KNOWN_RESOURCE;
headers.put(HttpHeaders.WWW_AUTHENTICATE, "Bearer resource_metadata=" +
"\"" + resourceMetadata + "\","
+ " error=\"invalid_token\","
+ " error_description=\"Access token is missing or expired\"");
String resourceMetadata = APIConstants.HTTPS_PROTOCOL + APIConstants.URL_SCHEME_SEPARATOR +
hostHeader + contextPath + APIMgtGatewayConstants.MCP_WELL_KNOWN_RESOURCE;
String dcrEndpoint = getDcrEndpoint();
String wwwAuthenticate = "Bearer resource_metadata=\"" + resourceMetadata + "\"";
if (StringUtils.isNotEmpty(dcrEndpoint)) {
Comment on lines +802 to +806
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

resource_metadata URL construction now hardcodes https:// and uses hostHeader directly, which can omit the gateway port when the Host header is missing/invalid and you fall back to APIUtil.getHostAddress(). Previously MCPUtils.getGatewayServerURL(...) used vhost httpsUrl (including port). Consider restoring vhost-based resolution or appending the configured HTTPS port/offset when building the URL.

Copilot uses AI. Check for mistakes.
wwwAuthenticate += ", dcr=\"" + dcrEndpoint + "\"";
}
wwwAuthenticate += ", error=\"invalid_token\", error_description=\"Access token is missing or expired\"";
headers.put(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate);
}
} else {
headers.put(HttpHeaders.WWW_AUTHENTICATE, getAuthenticatorsChallengeString() +
Expand Down Expand Up @@ -969,6 +967,38 @@ public void setKeyManagers(String keyManagers) {
this.keyManagers = keyManagers;
}

private String getDcrEndpoint() {
if (StringUtils.isEmpty(apiUUID)) {
return null;
}
List<String> keyManagers = DataHolder.getInstance().getKeyManagersFromUUID(apiUUID);
if (keyManagers == null || keyManagers.isEmpty()) {
return null;
}

String tenantDomain = GatewayUtils.getTenantDomain();
KeyManagerDto keyManagerDto = null;
if (APIConstants.KeyManager.API_LEVEL_ALL_KEY_MANAGERS.equals(keyManagers.get(0))) {
Map<String, KeyManagerDto> keyManagerMap = KeyManagerHolder.getTenantKeyManagers(tenantDomain);
if (keyManagerMap.size() == 1) {
keyManagerDto = keyManagerMap.values().iterator().next();
}
} else if (keyManagers.size() == 1) {
keyManagerDto = KeyManagerHolder.getKeyManagerByName(tenantDomain, keyManagers.get(0));
}

if (keyManagerDto != null && keyManagerDto.getKeyManager() != null) {
try {
org.wso2.carbon.apimgt.api.model.KeyManagerConfiguration config =
keyManagerDto.getKeyManager().getKeyManagerConfiguration();
return (String) config.getParameter(APIConstants.KeyManager.CLIENT_REGISTRATION_ENDPOINT);
} catch (APIManagementException e) {
Comment on lines +989 to +995
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: 4

Suggested change
if (keyManagerDto != null && keyManagerDto.getKeyManager() != null) {
try {
org.wso2.carbon.apimgt.api.model.KeyManagerConfiguration config =
keyManagerDto.getKeyManager().getKeyManagerConfiguration();
return (String) config.getParameter(APIConstants.KeyManager.CLIENT_REGISTRATION_ENDPOINT);
} catch (APIManagementException e) {
if (keyManagerDto != null && keyManagerDto.getKeyManager() != null) {
try {
if (log.isDebugEnabled()) {
log.debug("Retrieving DCR endpoint from key manager: " + keyManagerDto.getName());
}
org.wso2.carbon.apimgt.api.model.KeyManagerConfiguration config =
keyManagerDto.getKeyManager().getKeyManagerConfiguration();
return (String) config.getParameter(APIConstants.KeyManager.CLIENT_REGISTRATION_ENDPOINT);
} catch (APIManagementException e) {
log.error("Error while retrieving key manager configuration for MCP DCR support", e);

log.error("Error while retrieving key manager configuration for MCP DCR support", e);
}
}
return null;
}

public boolean isMCPGetRequest(MessageContext messageContext) {
String path = (String) messageContext.getProperty(APIMgtGatewayConstants.API_ELECTED_RESOURCE);
String httpMethod = (String) messageContext.getProperty(APIMgtGatewayConstants.HTTP_METHOD);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
import java.security.cert.Certificate;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -78,6 +77,8 @@
import java.util.Set;
import javax.cache.Cache;

import static org.wso2.carbon.apimgt.impl.APIConstants.MCP_HTTP_METHOD;

/**
* A Validator class to validate JWT tokens in an API request.
*/
Expand Down Expand Up @@ -173,7 +174,7 @@ public AuthenticationContext authenticate(SignedJWTInfo signedJWTInfo, MessageCo

String apiType = (String) synCtx.getProperty(APIMgtGatewayConstants.API_TYPE);
if (org.apache.commons.lang3.StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) {
Object mcpMethodProperty = synCtx.getProperty("MCP_HTTP_METHOD");
Object mcpMethodProperty = synCtx.getProperty(MCP_HTTP_METHOD);
Comment on lines 175 to +177
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: 5

Suggested change
String apiType = (String) synCtx.getProperty(APIMgtGatewayConstants.API_TYPE);
if (org.apache.commons.lang3.StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) {
Object mcpMethodProperty = synCtx.getProperty("MCP_HTTP_METHOD");
Object mcpMethodProperty = synCtx.getProperty(MCP_HTTP_METHOD);
String apiType = (String) synCtx.getProperty(APIMgtGatewayConstants.API_TYPE);
if (org.apache.commons.lang3.StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) {
log.debug("Processing MCP API type request");

if (mcpMethodProperty != null) {
httpMethod = mcpMethodProperty.toString();
}
Comment on lines +177 to 180
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: 6

Suggested change
Object mcpMethodProperty = synCtx.getProperty(MCP_HTTP_METHOD);
if (mcpMethodProperty != null) {
httpMethod = mcpMethodProperty.toString();
}
Object mcpMethodProperty = synCtx.getProperty(MCP_HTTP_METHOD);
if (mcpMethodProperty != null) {
httpMethod = mcpMethodProperty.toString();
log.debug("MCP HTTP method set to: " + httpMethod);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
import java.util.Set;
import javax.cache.Cache;

import static org.wso2.carbon.apimgt.impl.APIConstants.MCP_HTTP_METHOD;

/**
* An API consumer authenticator which authenticates user requests using
* the OAuth protocol. This implementation uses some default token/delimiter
Expand Down Expand Up @@ -224,7 +226,7 @@ public AuthenticationResponse authenticate(MessageContext synCtx) throws APIMana
String matchingResource = (String) synCtx.getProperty(APIConstants.API_ELECTED_RESOURCE);

if (StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) {
httpMethod = synCtx.getProperty("MCP_HTTP_METHOD").toString();
httpMethod = synCtx.getProperty(MCP_HTTP_METHOD).toString();
matchingResource = (String) synCtx.getProperty("MCP_API_ELECTED_RESOURCE");
}
Comment on lines 228 to 231
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: 7

Suggested change
if (StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) {
httpMethod = synCtx.getProperty("MCP_HTTP_METHOD").toString();
httpMethod = synCtx.getProperty(MCP_HTTP_METHOD).toString();
matchingResource = (String) synCtx.getProperty("MCP_API_ELECTED_RESOURCE");
}
if (StringUtils.equals(APIConstants.API_TYPE_MCP, apiType)) {
httpMethod = synCtx.getProperty(MCP_HTTP_METHOD).toString();
matchingResource = (String) synCtx.getProperty("MCP_API_ELECTED_RESOURCE");
log.debug("Processing MCP API request with HTTP method: " + httpMethod + " and resource: " + matchingResource);

SignedJWTInfo signedJWTInfo = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,11 @@ public static List<String> getAllScopes(API api) {
for (URLMapping urlMapping : api.getResources()) {
if (urlMapping.getScopes() != null) {
allScopes.addAll(urlMapping.getScopes());
allScopes.add("default");
}
}
}
allScopes.add("default");
return allScopes;
Comment on lines 336 to 341
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

getAllScopes now unconditionally adds the literal "default" inside the loop and again after the loop, which can create duplicates (and repeats it once per resource that has scopes). Consider using a Set or only adding "default" once to avoid duplicating values in the published scopes_supported list.

Copilot uses AI. Check for mistakes.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ public static boolean validateRequest(McpRequest request) throws McpException {
throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE,
APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE, "Missing method field");
}
if (!APIConstants.MCP.ALLOWED_METHODS.contains(method)) {
throw new McpException(APIConstants.MCP.RpcConstants.METHOD_NOT_FOUND_CODE,
APIConstants.MCP.RpcConstants.METHOD_NOT_FOUND_MESSAGE, "Method " + method + " not found");
}

Object id = request.getId();
if (id == null && !APIConstants.MCP.METHOD_NOTIFICATION_INITIALIZED.equals(method)) {
Expand Down Expand Up @@ -150,13 +146,7 @@ public static void validateInitializeRequest(Object id, McpRequest requestObject
if (requestObject.getParams() != null) {
Params params = requestObject.getParams();
String protocolVersion = params.getProtocolVersion();
if (!StringUtils.isEmpty(protocolVersion)) {
if (!APIConstants.MCP.SUPPORTED_PROTOCOL_VERSIONS.contains(protocolVersion)) {
throw new McpExceptionWithId(id, APIConstants.MCP.RpcConstants.INVALID_PARAMS_CODE,
APIConstants.MCP.PROTOCOL_MISMATCH_ERROR,
MCPPayloadGenerator.getInitializeErrorBody(protocolVersion));
}
} else {
if (StringUtils.isEmpty(protocolVersion)) {
throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE,
APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE, "Missing protocolVersion field");
}
Comment on lines +149 to 152
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: 9

Suggested change
if (StringUtils.isEmpty(protocolVersion)) {
throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE,
APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE, "Missing protocolVersion field");
}
if (StringUtils.isEmpty(protocolVersion)) {
log.error("Validation failed: Missing protocolVersion field in initialize request");
throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE,
APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE, "Missing protocolVersion field");
}

Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

validateInitializeRequest no longer validates protocolVersion against APIConstants.MCP.SUPPORTED_PROTOCOL_VERSIONS, so unsupported protocol versions will now be accepted. If protocol negotiation is required, reintroduce the supported-version check (and return the existing protocol-mismatch error payload) rather than only checking for non-empty.

Suggested change
}
}
if (!APIConstants.MCP.SUPPORTED_PROTOCOL_VERSIONS.contains(protocolVersion)) {
throw new McpException(APIConstants.MCP.RpcConstants.INVALID_REQUEST_CODE,
APIConstants.MCP.RpcConstants.INVALID_REQUEST_MESSAGE,
"Unsupported protocolVersion: " + protocolVersion);
}

Copilot uses AI. Check for mistakes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
Expand Down Expand Up @@ -33,12 +32,17 @@
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.wso2.carbon.apimgt.api.model.KeyManager;
import org.wso2.carbon.apimgt.api.model.KeyManagerConfiguration;
import org.wso2.carbon.apimgt.common.gateway.extensionlistener.ExtensionListener;
import org.wso2.carbon.apimgt.gateway.APIMgtGatewayConstants;
import org.wso2.carbon.apimgt.gateway.internal.DataHolder;
import org.wso2.carbon.apimgt.gateway.utils.GatewayUtils;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.APIManagerConfiguration;
import org.wso2.carbon.apimgt.impl.APIManagerConfigurationService;
import org.wso2.carbon.apimgt.impl.dto.KeyManagerDto;
import org.wso2.carbon.apimgt.impl.factory.KeyManagerHolder;
import org.wso2.carbon.apimgt.impl.dto.ExtendedJWTConfigurationDto;
import org.wso2.carbon.apimgt.impl.internal.ServiceReferenceHolder;
import org.wso2.carbon.apimgt.impl.utils.APIUtil;
Expand All @@ -47,7 +51,10 @@
import org.wso2.carbon.metrics.manager.Timer;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import java.util.ArrayList;
import java.util.Collections;
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Unused import java.util.Collections will fail compilation (unused imports are errors in Java). Please remove it or use it.

Suggested change
import java.util.Collections;

Copilot uses AI. Check for mistakes.
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

Expand All @@ -56,8 +63,8 @@
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({Util.class, MetricManager.class, Timer.Context.class, APIUtil.class, GatewayUtils.class,
ServiceReferenceHolder.class, MultitenantUtils.class, APIKeyValidator.class,
org.wso2.carbon.apimgt.gateway.internal.ServiceReferenceHolder.class,})
ServiceReferenceHolder.class, MultitenantUtils.class, APIKeyValidator.class, KeyManagerHolder.class,
DataHolder.class, org.wso2.carbon.apimgt.gateway.internal.ServiceReferenceHolder.class})
public class APIAuthenticationHandlerTestCase {

private Timer.Context context;
Expand Down Expand Up @@ -246,6 +253,70 @@ public void testDestroy() {
apiAuthenticationHandler.destroy();
}

@Test
public void testHandleRequestForMCPNoAuth() {
APIAuthenticationHandler apiAuthenticationHandler = createAPIAuthenticationHandler();
apiAuthenticationHandler.setApiType(APIConstants.API_TYPE_MCP);
apiAuthenticationHandler.init(synapseEnvironment);

Mockito.when(messageContext.getProperty(APIMgtGatewayConstants.MCP_NO_AUTH_REQUEST)).thenReturn(true);
Mockito.when(messageContext.getProperty(APIMgtGatewayConstants.MCP_METHOD)).thenReturn("initialize");

Options options = Mockito.mock(Options.class);
Mockito.when(options.getMessageId()).thenReturn("1");
Mockito.when(axis2MsgCntxt.getOptions()).thenReturn(options);

TreeMap transportHeaders = new TreeMap();
Mockito.when(axis2MsgCntxt.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS)).thenReturn(transportHeaders);

Assert.assertTrue(apiAuthenticationHandler.handleRequest(messageContext));
Mockito.verify(messageContext).setProperty(APIMgtGatewayConstants.API_TYPE, APIConstants.API_TYPE_MCP);
}

@Test
public void testHandleAuthFailureForMCPWithDCR() throws Exception {
APIAuthenticationHandler apiAuthenticationHandler = createAPIAuthenticationHandlerForExceptionTest();
apiAuthenticationHandler.setApiType(APIConstants.API_TYPE_MCP);
String apiUUID = "1234-5678";
apiAuthenticationHandler.setApiUUID(apiUUID);
apiAuthenticationHandler.init(synapseEnvironment);

PowerMockito.mockStatic(DataHolder.class);
DataHolder dataHolder = Mockito.mock(DataHolder.class);
Mockito.when(DataHolder.getInstance()).thenReturn(dataHolder);
List<String> keyManagers = new ArrayList<>();
keyManagers.add("default");
Mockito.when(dataHolder.getKeyManagersFromUUID(apiUUID)).thenReturn(keyManagers);

PowerMockito.mockStatic(KeyManagerHolder.class);
KeyManagerDto keyManagerDto = Mockito.mock(KeyManagerDto.class);
Mockito.when(KeyManagerHolder.getKeyManagerByName(Mockito.anyString(), Mockito.eq("default"))).thenReturn(keyManagerDto);
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

This test stubs KeyManagerHolder.getKeyManagerByName(anyString(), "default"), but GatewayUtils is statically mocked in setup() so GatewayUtils.getTenantDomain() returns null; anyString() won’t match null and the stub won’t apply, causing getDcrEndpoint() to return null and the assertion to fail. Either stub GatewayUtils.getTenantDomain() to a non-null tenant domain, or change the matcher to accept null (e.g., Mockito.any() for the first arg).

Suggested change
Mockito.when(KeyManagerHolder.getKeyManagerByName(Mockito.anyString(), Mockito.eq("default"))).thenReturn(keyManagerDto);
Mockito.when(KeyManagerHolder.getKeyManagerByName(Mockito.any(), Mockito.eq("default"))).thenReturn(keyManagerDto);

Copilot uses AI. Check for mistakes.

KeyManager keyManager = Mockito.mock(KeyManager.class);
Mockito.when(keyManagerDto.getKeyManager()).thenReturn(keyManager);
KeyManagerConfiguration kmConfig = Mockito.mock(KeyManagerConfiguration.class);
Mockito.when(keyManager.getKeyManagerConfiguration()).thenReturn(kmConfig);
String dcrEndpoint = "https://localhost:9443/client-registration/v0.17/register";
Mockito.when(kmConfig.getParameter(APIConstants.KeyManager.CLIENT_REGISTRATION_ENDPOINT)).thenReturn(dcrEndpoint);

Options options = Mockito.mock(Options.class);
Mockito.when(options.getMessageId()).thenReturn("1");
Mockito.when(axis2MsgCntxt.getOptions()).thenReturn(options);

TreeMap transportHeaders = new TreeMap();
Mockito.when(axis2MsgCntxt.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS)).thenReturn(transportHeaders);
Mockito.when(messageContext.getProperty(RESTConstants.REST_API_CONTEXT)).thenReturn("/mcp/1.0.0");
PowerMockito.when(APIUtil.getHostAddress()).thenReturn("localhost");
PowerMockito.when(APIUtil.getPortOffset()).thenReturn(0);

Assert.assertFalse(apiAuthenticationHandler.handleRequest(messageContext));

String wwwAuthenticate = (String) transportHeaders.get("WWW-Authenticate");
Assert.assertNotNull(wwwAuthenticate);
Assert.assertTrue(wwwAuthenticate.contains("dcr=\"" + dcrEndpoint + "\""));
Assert.assertTrue(wwwAuthenticate.contains("resource_metadata="));
}

/*
* This method will create an instance of APIAuthenticationHandler
* */
Expand Down Expand Up @@ -323,7 +394,7 @@ protected void initializeAuthenticators() {}

@Override
protected boolean isAuthenticate(MessageContext messageContext) throws APISecurityException {
throw new APISecurityException(1000, "test");
throw new APISecurityException(APISecurityConstants.API_AUTH_INVALID_CREDENTIALS, "test");
}

@Override
Expand Down Expand Up @@ -366,19 +437,16 @@ public void testStartMetricTimer(){
.thenReturn("org.wso2.amAPIAuthenticationHandler");
PowerMockito.when(MetricManager.timer(org.wso2.carbon.metrics.manager.Level.INFO, "org.wso2.amAPIAuthenticationHandler"))
.thenReturn(timer);

Timer.Context returned = apiAuthenticationHandler.startMetricTimer();
Assert.assertSame(localCtx, returned); // assert return
Mockito.verify(timer, Mockito.times(1)).start();
apiAuthenticationHandler.startMetricTimer();
Mockito.verify(timer).start();
}

@Test
public void testStopMetricTimer(){
APIAuthenticationHandler apiAuthenticationHandler = new APIAuthenticationHandler();
Mockito.when(context.stop()).thenReturn(1000L);
apiAuthenticationHandler.stopMetricTimer(context);
Assert.assertTrue(true);
Mockito.verify(context).stop();
}

}

Loading
Loading