Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Priority order:

| Setting | Default | Required | Description |
|---------------------------------------------------|:-----------------:|:--------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| toolsets.security.allowedRedirectUris | [] | No | List of allowed redirect URIs that clients can provide during OAuth sign-in. Validated against this list and the toolset's own `redirect_uri`. For dynamic client registration, all URIs from this list are registered with the authorization server. |
| toolsets.security.authorizationServers | - | No | Path(s) to the authorization server URLs trusted to issue access tokens for MCP clients. |
| toolsets.security.resourceSchema | https | No | Schema of the resource server. This URL schema is used to construct the resource identifier for token validation, as defined in RFC 9728. If not specified, the default value will be applied. |
| toolsets.security.resourceHost | - | No | The public, fully-qualified hostname of this resource server (e.g., api.example.com). This is used to construct the resource identifier for token validation per RFC 9728. If not set, the host is derived from the incoming request. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ public class ResourceSignInRequest {

@JsonAlias({"apiKey", "api_key"})
private String apiKey;

@JsonAlias({"redirectUri", "redirect_uri"})
private String redirectUri;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.epam.aidial.core.credentials.service;

import com.epam.aidial.core.config.ResourceAuthSettings;
import lombok.experimental.UtilityClass;

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

@UtilityClass
public class RedirectUriHelper {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we don't need the helper any longer


/**
* Builds the effective list of allowed redirect URIs by combining the global allowed list
* with the toolset's own redirect URI (if present and not already included).
*/
public List<String> collectAllowedRedirectUris(List<String> globalAllowedUris, ResourceAuthSettings resourceAuthSettings) {
List<String> uris = new ArrayList<>(globalAllowedUris);
if (resourceAuthSettings.getRedirectUri() != null && !uris.contains(resourceAuthSettings.getRedirectUri())) {
uris.add(resourceAuthSettings.getRedirectUri());
}
return uris;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,30 @@
import com.epam.aidial.core.credentials.data.credentials.TokenResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

@AllArgsConstructor
@Slf4j
public class TokenService {

private final ResourceAuthorizationClient resourceAuthorizationClient;
private final List<String> allowedRedirectUris;

public TokenResponse getToken(String resourceId,
ResourceAuthSettings resourceAuthSettings,
ResourceSignInRequest resourceSignInRequest) {
log.debug("Start Resource {} token retrieval", resourceId);
String redirectUri = resolveRedirectUri(resourceAuthSettings, resourceSignInRequest);
TokenRequest tokenRequest = TokenRequest.builder()
.clientId(resourceAuthSettings.getClientId())
.clientSecret(resourceAuthSettings.getClientSecret())
.code(resourceSignInRequest.getCode())
// TODO: do we need to support different?
.grantType("authorization_code")
.codeVerifier(resourceAuthSettings.getCodeVerifier())
.redirectUri(resourceAuthSettings.getRedirectUri())
.redirectUri(redirectUri)
.build();

TokenResponse tokenResponse = doTokenCall(resourceAuthSettings.getTokenEndpoint(), tokenRequest.buildFormData());
Expand All @@ -36,7 +41,7 @@ public TokenResponse getToken(String resourceId,
public TokenResponse getToken(String resourceId,
ResourceAuthSettings resourceAuthSettings,
String refreshToken) {
log.debug("Start Resource {} reresh token retrieval", resourceId);
log.debug("Start Resource {} refresh token retrieval", resourceId);
RefreshTokenRequest tokenRequest = RefreshTokenRequest.builder()
.clientId(resourceAuthSettings.getClientId())
.clientSecret(resourceAuthSettings.getClientSecret())
Expand All @@ -49,6 +54,27 @@ public TokenResponse getToken(String resourceId,
return tokenResponse;
}

private String resolveRedirectUri(ResourceAuthSettings resourceAuthSettings,
ResourceSignInRequest resourceSignInRequest) {
String requestRedirectUri = resourceSignInRequest.getRedirectUri();

if (StringUtils.isNotBlank(requestRedirectUri)) {
List<String> effectiveAllowedUris = getEffectiveAllowedUris(resourceAuthSettings);
Copy link
Collaborator

Choose a reason for hiding this comment

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

It doesn't make sense to build a new list and check if the url is presented there.
just allowed.contains()

if (!effectiveAllowedUris.contains(requestRedirectUri)) {
throw new IllegalArgumentException(
"Provided redirect_uri is not in the list of allowed redirect URIs");
}
return requestRedirectUri;
}

// Fallback to toolset's own redirect_uri (backward compatible)
return resourceAuthSettings.getRedirectUri();
}

private List<String> getEffectiveAllowedUris(ResourceAuthSettings resourceAuthSettings) {
return RedirectUriHelper.collectAllowedRedirectUris(allowedRedirectUris, resourceAuthSettings);
}

private TokenResponse doTokenCall(String tokenEndpoint, String tokenRequest) {
return resourceAuthorizationClient.executePost(
tokenEndpoint, tokenRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.epam.aidial.core.credentials.data.registration.ClientRegistration;
import com.epam.aidial.core.credentials.data.registration.ClientRegistrationRequest;
import com.epam.aidial.core.credentials.data.registration.ClientRegistrationResponse;
import com.epam.aidial.core.credentials.service.RedirectUriHelper;
import com.epam.aidial.core.credentials.service.ResourceAuthorizationClient;
import com.epam.aidial.core.credentials.service.metadata.AuthorizationServerMetadataService;
import com.epam.aidial.core.credentials.service.metadata.ProtectedResourceMetadataService;
Expand Down Expand Up @@ -40,6 +41,7 @@ public class DynamicResourceRegistrationStrategy implements ResourceRegistration
private final AuthorizationServerMetadataService authorizationServerMetadataService;
private final ResourceAuthorizationClient resourceAuthorizationClient;
private final ProtectedResourceMetadataService protectedResourceMetadataService;
private final List<String> allowedRedirectUris;

/**
* Registers a protected resource dynamically using the authorization server's dynamic client registration
Expand Down Expand Up @@ -72,7 +74,7 @@ public ClientRegistration register(String resourceId, String resourceEndpoint, R

ClientRegistrationRequest clientRegistrationRequest = ClientRegistrationRequest.builder()
.clientName(resourceId)
.redirectUris(List.of(resourceAuthSettings.getRedirectUri()))
.redirectUris(collectRedirectUris(resourceAuthSettings))
.build();

ClientRegistrationResponse clientRegistrationResponse = resourceAuthorizationClient.executePost(
Expand All @@ -97,4 +99,8 @@ public ClientRegistration register(String resourceId, String resourceEndpoint, R
log.info("Finished dynamic registration for Resource: {}", resourceId);
return clientRegistration;
}

private List<String> collectRedirectUris(ResourceAuthSettings resourceAuthSettings) {
return RedirectUriHelper.collectAllowedRedirectUris(allowedRedirectUris, resourceAuthSettings);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the more simple way is just to create a hashed set and add the url there.
If the list is relatively small. ArrayList works even better.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,24 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

@Slf4j
@RequiredArgsConstructor
public class ResourceRegistrationService {

private final AuthorizationServerMetadataService authorizationServerMetadataService;
private final ResourceAuthorizationClient resourceAuthorizationClient;
private final ProtectedResourceMetadataService protectedResourceMetadataService;
private final List<String> allowedRedirectUris;

public ClientRegistration register(String resourceId,
String resourceEndpoint,
ResourceAuthSettings resourceAuthSettings,
boolean oauthDynamicClientRegistrationRequired) {
ResourceRegistrationStrategy strategy = oauthDynamicClientRegistrationRequired
? new DynamicResourceRegistrationStrategy(
authorizationServerMetadataService, resourceAuthorizationClient, protectedResourceMetadataService)
authorizationServerMetadataService, resourceAuthorizationClient, protectedResourceMetadataService, allowedRedirectUris)
: new StaticResourceRegistrationStrategy(
authorizationServerMetadataService, protectedResourceMetadataService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ protected ResourceAuthSettingsValidationFields getValidationFields(ResourceAuthS
private ResourceAuthSettingsValidationFields getOauthWithStaticRegistrationValidationFields() {
return ResourceAuthSettingsValidationFields.builder()
.requiredFields(Set.of(
ResourceAuthSettingsField.REDIRECT_URI,
ResourceAuthSettingsField.CLIENT_ID,
ResourceAuthSettingsField.CLIENT_SECRET,
ResourceAuthSettingsField.AUTHORIZATION_ENDPOINT,
Expand Down Expand Up @@ -90,7 +89,7 @@ private ResourceAuthSettingsValidationFields getOauthWithStaticRegistrationValid
*/
private ResourceAuthSettingsValidationFields getOauthWithDynamicRegistrationValidationFields() {
return ResourceAuthSettingsValidationFields.builder()
.requiredFields(Set.of(ResourceAuthSettingsField.REDIRECT_URI))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is redirect uri not required any longer?

.requiredFields(Set.of())
.forbiddenFields(Set.of(
ResourceAuthSettingsField.CLIENT_ID,
ResourceAuthSettingsField.CLIENT_SECRET,
Expand Down Expand Up @@ -120,7 +119,6 @@ private ResourceAuthSettingsValidationFields getOauthWithDynamicRegistrationVali
private ResourceAuthSettingsValidationFields getOauthWithNoAuthTypeChangeValidationFields() {
return ResourceAuthSettingsValidationFields.builder()
.requiredFields(Set.of(
ResourceAuthSettingsField.REDIRECT_URI,
ResourceAuthSettingsField.CLIENT_ID,
ResourceAuthSettingsField.AUTHORIZATION_ENDPOINT,
ResourceAuthSettingsField.TOKEN_ENDPOINT)
Expand Down
Loading
Loading