diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.common/src/main/java/org/wso2/carbon/identity/api/server/application/management/common/ApplicationManagementConstants.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.common/src/main/java/org/wso2/carbon/identity/api/server/application/management/common/ApplicationManagementConstants.java index bd6a8f5489..579904570c 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.common/src/main/java/org/wso2/carbon/identity/api/server/application/management/common/ApplicationManagementConstants.java +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.common/src/main/java/org/wso2/carbon/identity/api/server/application/management/common/ApplicationManagementConstants.java @@ -250,7 +250,17 @@ public enum ErrorMessage { // Organization application based issuer configuration related error messages. ERROR_RETRIEVING_ALLOWED_ISSUERS("65603", "Error occurred while retrieving allowed issuers for the tenant.", - "Unexpected error occurred while retrieving allowed issuers for the tenant."); + "Unexpected error occurred while retrieving allowed issuers for the tenant."), + + // FAPI metadata related error messages. + ERROR_RETRIEVING_FAPI_METADATA("65604", + "Error occurred while retrieving FAPI metadata.", + "Unexpected error occurred while retrieving FAPI metadata for tenant: %s."), + + // FAPI profile validation related error messages. + UNSUPPORTED_FAPI_PROFILE("65605", + "Unsupported FAPI profile.", + "The provided FAPI profile '%s' is not supported by the organization. Supported profiles: %s."); private final String code; private final String message; diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.common/src/main/java/org/wso2/carbon/identity/api/server/application/management/common/ApplicationManagementServiceHolder.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.common/src/main/java/org/wso2/carbon/identity/api/server/application/management/common/ApplicationManagementServiceHolder.java index 124acd7814..bb876390bb 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.common/src/main/java/org/wso2/carbon/identity/api/server/application/management/common/ApplicationManagementServiceHolder.java +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.common/src/main/java/org/wso2/carbon/identity/api/server/application/management/common/ApplicationManagementServiceHolder.java @@ -31,6 +31,7 @@ import org.wso2.carbon.identity.oauth.ciba.api.CibaAuthServiceImpl; import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; import org.wso2.carbon.identity.oauth2.config.services.OAuth2OIDCConfigOrgUsageScopeMgtService; +import org.wso2.carbon.identity.oauth2.fapi.services.FapiConfigMgtService; import org.wso2.carbon.identity.organization.management.application.OrgApplicationManager; import org.wso2.carbon.identity.sso.saml.SAMLSSOConfigServiceImpl; import org.wso2.carbon.identity.template.mgt.TemplateManager; @@ -151,6 +152,12 @@ private static class OAuthOIDCConfigOrgUsageScopeMgtServiceHolder { .getOSGiService(OAuth2OIDCConfigOrgUsageScopeMgtService.class, null); } + private static class FapiConfigMgtServiceHolder { + + static final FapiConfigMgtService SERVICE = (FapiConfigMgtService) PrivilegedCarbonContext + .getThreadLocalCarbonContext().getOSGiService(FapiConfigMgtService.class, null); + } + /** * Get ApplicationManagementService. * @@ -289,4 +296,14 @@ public static OAuth2OIDCConfigOrgUsageScopeMgtService getOAuth2OIDCConfigOrgUsag return OAuthOIDCConfigOrgUsageScopeMgtServiceHolder.SERVICE; } + + /** + * Get FapiConfigMgtService. + * + * @return FapiConfigMgtService. + */ + public static FapiConfigMgtService getFapiConfigMgtService() { + + return FapiConfigMgtServiceHolder.SERVICE; + } } diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/FapiMetadata.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/FapiMetadata.java index 0548c852d9..8618cff220 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/FapiMetadata.java +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/FapiMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-2026, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -33,11 +33,30 @@ import javax.xml.bind.annotation.*; public class FapiMetadata { - + + private MetadataProperty allowedFapiProfiles; private MetadataProperty allowedSignatureAlgorithms; private MetadataProperty allowedEncryptionAlgorithms; private ClientAuthenticationMethodMetadata tokenEndpointAuthMethod; + /** + **/ + public FapiMetadata allowedFapiProfiles(MetadataProperty allowedFapiProfiles) { + + this.allowedFapiProfiles = allowedFapiProfiles; + return this; + } + + @ApiModelProperty(value = "") + @JsonProperty("allowedFapiProfiles") + @Valid + public MetadataProperty getAllowedFapiProfiles() { + return allowedFapiProfiles; + } + public void setAllowedFapiProfiles(MetadataProperty allowedFapiProfiles) { + this.allowedFapiProfiles = allowedFapiProfiles; + } + /** **/ public FapiMetadata allowedSignatureAlgorithms(MetadataProperty allowedSignatureAlgorithms) { @@ -104,14 +123,15 @@ public boolean equals(java.lang.Object o) { return false; } FapiMetadata fapiMetadata = (FapiMetadata) o; - return Objects.equals(this.allowedSignatureAlgorithms, fapiMetadata.allowedSignatureAlgorithms) && + return Objects.equals(this.allowedFapiProfiles, fapiMetadata.allowedFapiProfiles) && + Objects.equals(this.allowedSignatureAlgorithms, fapiMetadata.allowedSignatureAlgorithms) && Objects.equals(this.allowedEncryptionAlgorithms, fapiMetadata.allowedEncryptionAlgorithms) && Objects.equals(this.tokenEndpointAuthMethod, fapiMetadata.tokenEndpointAuthMethod); } @Override public int hashCode() { - return Objects.hash(allowedSignatureAlgorithms, allowedEncryptionAlgorithms, tokenEndpointAuthMethod); + return Objects.hash(allowedFapiProfiles, allowedSignatureAlgorithms, allowedEncryptionAlgorithms, tokenEndpointAuthMethod); } @Override @@ -119,7 +139,8 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class FapiMetadata {\n"); - + + sb.append(" allowedFapiProfiles: ").append(toIndentedString(allowedFapiProfiles)).append("\n"); sb.append(" allowedSignatureAlgorithms: ").append(toIndentedString(allowedSignatureAlgorithms)).append("\n"); sb.append(" allowedEncryptionAlgorithms: ").append(toIndentedString(allowedEncryptionAlgorithms)).append("\n"); sb.append(" tokenEndpointAuthMethod: ").append(toIndentedString(tokenEndpointAuthMethod)).append("\n"); diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/FapiProfile.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/FapiProfile.java new file mode 100644 index 0000000000..0d04a83634 --- /dev/null +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/FapiProfile.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.api.server.application.management.v1; + +import io.swagger.annotations.ApiModel; +import javax.validation.constraints.*; + +/** + * FAPI security profile. - `FAPI1_ADVANCED`: FAPI 1.0 Advanced Security Profile. - `FAPI2_SECURITY`: FAPI 2.0 Security Profile. + **/ +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlEnumValue; + +@XmlType(name="") +@XmlEnum(String.class) +public enum FapiProfile { + + @XmlEnumValue("FAPI1_ADVANCED") FAPI1_ADVANCED(String.valueOf("FAPI1_ADVANCED")), @XmlEnumValue("FAPI2_SECURITY") FAPI2_SECURITY(String.valueOf("FAPI2_SECURITY")); + + + private String value; + + FapiProfile(String v) { + value = v; + } + + public String value() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static FapiProfile fromValue(String value) { + for (FapiProfile b : FapiProfile.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } +} + + + diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/OpenIDConnectConfiguration.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/OpenIDConnectConfiguration.java index aa0a924bc2..5439a4132c 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/OpenIDConnectConfiguration.java +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/gen/java/org/wso2/carbon/identity/api/server/application/management/v1/OpenIDConnectConfiguration.java @@ -104,6 +104,7 @@ public static StateEnum fromValue(String value) { private SubjectConfiguration subject; private Boolean isFAPIApplication = false; private FapiMetadata fapiMetadata; + private FapiProfile fapiProfile = null; private CIBAAuthenticationRequestConfiguration cibaAuthenticationRequest; private AllowedIssuer issuer; @@ -538,6 +539,25 @@ public void setFapiMetadata(FapiMetadata fapiMetadata) { this.fapiMetadata = fapiMetadata; } + /** + * The FAPI security profile applied to the application. Applicable only when isFAPIApplication is true. If isFAPIApplication is true and this property is omitted, `FAPI1_ADVANCED` will be applied by default. + **/ + public OpenIDConnectConfiguration fapiProfile(FapiProfile fapiProfile) { + + this.fapiProfile = fapiProfile; + return this; + } + + @ApiModelProperty(example = "FAPI1_ADVANCED", value = "The FAPI security profile applied to the application. Applicable only when isFAPIApplication is true. If isFAPIApplication is true and this property is omitted, `FAPI1_ADVANCED` will be applied by default. ") + @JsonProperty("fapiProfile") + @Valid + public FapiProfile getFapiProfile() { + return fapiProfile; + } + public void setFapiProfile(FapiProfile fapiProfile) { + this.fapiProfile = fapiProfile; + } + /** **/ public OpenIDConnectConfiguration cibaAuthenticationRequest(CIBAAuthenticationRequestConfiguration cibaAuthenticationRequest) { @@ -608,6 +628,7 @@ public boolean equals(java.lang.Object o) { Objects.equals(this.subject, openIDConnectConfiguration.subject) && Objects.equals(this.isFAPIApplication, openIDConnectConfiguration.isFAPIApplication) && Objects.equals(this.fapiMetadata, openIDConnectConfiguration.fapiMetadata) && + Objects.equals(this.fapiProfile, openIDConnectConfiguration.fapiProfile) && Objects.equals(this.cibaAuthenticationRequest, openIDConnectConfiguration.cibaAuthenticationRequest) && Objects.equals(this.issuer, openIDConnectConfiguration.issuer); } @@ -646,6 +667,7 @@ public String toString() { sb.append(" subject: ").append(toIndentedString(subject)).append("\n"); sb.append(" isFAPIApplication: ").append(toIndentedString(isFAPIApplication)).append("\n"); sb.append(" fapiMetadata: ").append(toIndentedString(fapiMetadata)).append("\n"); + sb.append(" fapiProfile: ").append(toIndentedString(fapiProfile)).append("\n"); sb.append(" cibaAuthenticationRequest: ").append(toIndentedString(cibaAuthenticationRequest)).append("\n"); sb.append(" issuer: ").append(toIndentedString(issuer)).append("\n"); sb.append("}"); diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/ServerApplicationManagementService.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/ServerApplicationManagementService.java index eeb539e063..c1157d6caa 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/ServerApplicationManagementService.java +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/ServerApplicationManagementService.java @@ -142,6 +142,9 @@ import org.wso2.carbon.identity.cors.mgt.core.exception.CORSManagementServiceException; import org.wso2.carbon.identity.cors.mgt.core.model.CORSOrigin; import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; +import org.wso2.carbon.identity.oauth2.fapi.exceptions.FapiConfigMgtException; +import org.wso2.carbon.identity.oauth2.fapi.models.FapiProfileEnum; +import org.wso2.carbon.identity.oauth2.fapi.services.FapiConfigMgtService; import org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants; import org.wso2.carbon.identity.sso.saml.dto.SAMLSSOServiceProviderDTO; import org.wso2.carbon.identity.template.mgt.TemplateManager; @@ -190,6 +193,7 @@ import static org.wso2.carbon.identity.api.server.application.management.common.ApplicationManagementConstants.ErrorMessage.FORBIDDEN_OPERATION; import static org.wso2.carbon.identity.api.server.application.management.common.ApplicationManagementConstants.ErrorMessage.INBOUND_NOT_CONFIGURED; import static org.wso2.carbon.identity.api.server.application.management.common.ApplicationManagementConstants.ErrorMessage.UNSUPPORTED_ENHANCED_ORGANIZATION_AUTHENTICATION_ENABLED_CONFIGURATION; +import static org.wso2.carbon.identity.api.server.application.management.common.ApplicationManagementConstants.ErrorMessage.UNSUPPORTED_FAPI_PROFILE; import static org.wso2.carbon.identity.api.server.application.management.common.ApplicationManagementConstants.ErrorMessage.USE_EXTERNAL_CONSENT_PAGE_NOT_SUPPORTED; import static org.wso2.carbon.identity.api.server.application.management.common.ApplicationManagementConstants.ISSUER; import static org.wso2.carbon.identity.api.server.application.management.common.ApplicationManagementConstants.NAME; @@ -815,7 +819,10 @@ public String createApplication(ApplicationModel applicationModel, String templa */ if (applicationModel.getInboundProtocolConfiguration() != null && applicationModel.getInboundProtocolConfiguration().getOidc() != null) { - validateCORSOrigins(applicationModel.getInboundProtocolConfiguration().getOidc().getAllowedOrigins()); + OpenIDConnectConfiguration oidcConfig = + applicationModel.getInboundProtocolConfiguration().getOidc(); + validateCORSOrigins(oidcConfig.getAllowedOrigins()); + validateFapiProfile(oidcConfig); } /* @@ -1235,6 +1242,7 @@ public void putInboundOAuthConfiguration(String applicationId, OpenIDConnectConf * But for now we are handling the validation here. */ validateCORSOrigins(oidcConfigModel.getAllowedOrigins()); + validateFapiProfile(oidcConfigModel); putApplicationInbound(applicationId, oidcConfigModel, OAuthInboundFunctions::getInboundProtocolConfig); } @@ -2219,6 +2227,64 @@ private String buildFormattedDescription(String description, String... formatDat } } + /** + * Validates the FAPI profile in the given OIDC configuration. + * + *

Validation: checks the resolved profile against the organisation's allowed list from + * {@link FapiConfigMgtService}. Throws a 400 error if the profile is not supported. + * No-ops when {@code oidcConfig} is null, FAPI is disabled, or {@link FapiConfigMgtService} + * is unavailable (treated as optional OSGi service). + * + * @param oidcConfig the OIDC configuration from the incoming request; may be null. + */ + private void validateFapiProfile(OpenIDConnectConfiguration oidcConfig) { + + // Only validate when the application is marked as FAPI and a specific profile is provided. + // If isFAPIApplication is false the profile field is ignored in storage, so validation would + // produce a misleading error for a field that has no effect. + if (oidcConfig == null || !Boolean.TRUE.equals(oidcConfig.getIsFAPIApplication()) + || oidcConfig.getFapiProfile() == null) { + return; + } + + /* + * FapiConfigMgtService is treated as optional here. Profile validation is an enhancement — + * silently skipping it avoids blocking all OIDC application updates in deployments where + * the FAPI bundle is not present. The configs API module (/configs/fapi) hard-requires + * the service at startup and fails fast if it is absent. + */ + FapiConfigMgtService fapiConfigMgtService = ApplicationManagementServiceHolder.getFapiConfigMgtService(); + if (fapiConfigMgtService == null) { + log.debug("FapiConfigMgtService is unavailable. Skipping fapiProfile validation."); + return; + } + + String tenantDomain = ContextLoader.getTenantDomainFromContext(); + try { + List supportedProfiles = + fapiConfigMgtService.getFapiConfig(tenantDomain).getSupportedProfiles(); + String requestedProfileValue = oidcConfig.getFapiProfile().toString(); + + boolean isSupported = supportedProfiles.stream() + .anyMatch(p -> p.value().equals(requestedProfileValue)); + + if (!isSupported) { + String supportedProfilesStr = supportedProfiles.stream() + .map(FapiProfileEnum::value) + .collect(Collectors.joining(", ")); + if (log.isDebugEnabled()) { + log.debug(String.format("Rejected fapiProfile '%s' for tenant '%s'. Supported: [%s]", + requestedProfileValue, tenantDomain, supportedProfilesStr)); + } + throw buildClientError(UNSUPPORTED_FAPI_PROFILE, requestedProfileValue, supportedProfilesStr); + } + } catch (FapiConfigMgtException e) { + throw Utils.buildServerError( + String.format("Error while retrieving FAPI configuration for tenant '%s' during fapiProfile " + + "validation.", tenantDomain), e); + } + } + /** * Validate the CORS Origins. This should be moved to the service layer with the CORS adding step on creating * OIDC applications. diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/ServerApplicationMetadataService.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/ServerApplicationMetadataService.java index b2c5afc637..9e0606e84b 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/ServerApplicationMetadataService.java +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/ServerApplicationMetadataService.java @@ -18,6 +18,7 @@ package org.wso2.carbon.identity.api.server.application.management.v1.core; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -41,6 +42,7 @@ import org.wso2.carbon.identity.api.server.application.management.v1.SAMLMetaData; import org.wso2.carbon.identity.api.server.application.management.v1.WSTrustMetaData; import org.wso2.carbon.identity.api.server.application.management.v1.core.functions.Utils; +import org.wso2.carbon.identity.api.server.common.ContextLoader; import org.wso2.carbon.identity.api.server.common.error.APIError; import org.wso2.carbon.identity.application.common.model.Property; import org.wso2.carbon.identity.application.mgt.AbstractInboundAuthenticatorConfig; @@ -55,6 +57,10 @@ import org.wso2.carbon.identity.oauth2.config.exceptions.OAuth2OIDCConfigOrgUsageScopeMgtException; import org.wso2.carbon.identity.oauth2.config.models.IssuerDetails; import org.wso2.carbon.identity.oauth2.config.services.OAuth2OIDCConfigOrgUsageScopeMgtService; +import org.wso2.carbon.identity.oauth2.fapi.exceptions.FapiConfigMgtException; +import org.wso2.carbon.identity.oauth2.fapi.models.FapiConfig; +import org.wso2.carbon.identity.oauth2.fapi.models.FapiProfileEnum; +import org.wso2.carbon.identity.oauth2.fapi.services.FapiConfigMgtService; import org.wso2.carbon.identity.oauth2.model.ClientAuthenticationMethodModel; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.identity.sso.saml.SAMLSSOConfigServiceImpl; @@ -64,6 +70,7 @@ import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -89,6 +96,7 @@ public class ServerApplicationMetadataService { private final OAuthAdminServiceImpl oAuthAdminService; private final STSAdminServiceInterface sTSAdminServiceInterface; private final CibaAuthServiceImpl cibaAuthService; + private final FapiConfigMgtService fapiConfigMgtService; @Deprecated public ServerApplicationMetadataService(ApplicationManagementService applicationManagementService, @@ -96,24 +104,35 @@ public ServerApplicationMetadataService(ApplicationManagementService application OAuthAdminServiceImpl oAuthAdminService, STSAdminServiceInterface sTSAdminServiceInterface) { - this.applicationManagementService = applicationManagementService; - this.samlSSOConfigService = samlSSOConfigService; - this.oAuthAdminService = oAuthAdminService; - this.sTSAdminServiceInterface = sTSAdminServiceInterface; - this.cibaAuthService = ApplicationManagementServiceHolder.getCibaAuthService(); + this(applicationManagementService, samlSSOConfigService, oAuthAdminService, sTSAdminServiceInterface, + ApplicationManagementServiceHolder.getCibaAuthService(), + ApplicationManagementServiceHolder.getFapiConfigMgtService()); } + @Deprecated public ServerApplicationMetadataService(ApplicationManagementService applicationManagementService, SAMLSSOConfigServiceImpl samlSSOConfigService, OAuthAdminServiceImpl oAuthAdminService, STSAdminServiceInterface sTSAdminServiceInterface, CibaAuthServiceImpl cibaAuthService) { + this(applicationManagementService, samlSSOConfigService, oAuthAdminService, sTSAdminServiceInterface, + cibaAuthService, ApplicationManagementServiceHolder.getFapiConfigMgtService()); + } + + public ServerApplicationMetadataService(ApplicationManagementService applicationManagementService, + SAMLSSOConfigServiceImpl samlSSOConfigService, + OAuthAdminServiceImpl oAuthAdminService, + STSAdminServiceInterface sTSAdminServiceInterface, + CibaAuthServiceImpl cibaAuthService, + FapiConfigMgtService fapiConfigMgtService) { + this.applicationManagementService = applicationManagementService; this.samlSSOConfigService = samlSSOConfigService; this.oAuthAdminService = oAuthAdminService; this.sTSAdminServiceInterface = sTSAdminServiceInterface; this.cibaAuthService = cibaAuthService; + this.fapiConfigMgtService = fapiConfigMgtService; } private static final Log LOG = LogFactory.getLog(ServerApplicationMetadataService.class); @@ -261,6 +280,7 @@ public OIDCMetaData getOIDCMetadata() { supportedClientAuthMethods.stream().filter(clientAuthenticationMethod -> fapiAllowedAuthMethods.contains(clientAuthenticationMethod.getName())).collect(Collectors.toList()); FapiMetadata fapiMetadata = new FapiMetadata(); + fapiMetadata.allowedFapiProfiles(new MetadataProperty().options(getAllowedFapiProfiles())); fapiMetadata.allowedSignatureAlgorithms(new MetadataProperty().options(fapiAllowedSignatureAlgorithms)); fapiMetadata.allowedEncryptionAlgorithms(new MetadataProperty().options(fapiAllowedEncryptionAlgorithms)); fapiMetadata.setTokenEndpointAuthMethod(new ClientAuthenticationMethodMetadata() @@ -527,6 +547,33 @@ private List getAllowedIssuersForOrganization() { } } + /** + * Retrieves the list of allowed FAPI profiles for the current tenant from FapiConfigMgtService. + * Returns an empty list if the service is unavailable or no profiles are configured. + * + * @return List of allowed FAPI profile string values. + */ + private List getAllowedFapiProfiles() { + + if (fapiConfigMgtService == null) { + LOG.debug("FapiConfigMgtService is not available. Returning empty FAPI profiles list."); + return Collections.emptyList(); + } + String tenantDomain = ContextLoader.getTenantDomainFromContext(); + try { + FapiConfig fapiConfig = fapiConfigMgtService.getFapiConfig(tenantDomain); + List supportedProfiles = fapiConfig.getSupportedProfiles(); + if (CollectionUtils.isEmpty(supportedProfiles)) { + return Collections.emptyList(); + } + return supportedProfiles.stream() + .map(FapiProfileEnum::value) + .collect(Collectors.toList()); + } catch (FapiConfigMgtException e) { + throw handleServerError(ErrorMessage.ERROR_RETRIEVING_FAPI_METADATA, e); + } + } + /** * Handles the server errors and build APIError response with the given error message and exception. * diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/functions/application/inbound/oauth2/ApiModelToOAuthConsumerApp.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/functions/application/inbound/oauth2/ApiModelToOAuthConsumerApp.java index f3b7745bc8..ff67ead58c 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/functions/application/inbound/oauth2/ApiModelToOAuthConsumerApp.java +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/functions/application/inbound/oauth2/ApiModelToOAuthConsumerApp.java @@ -41,6 +41,7 @@ import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; import org.wso2.carbon.identity.oauth.dto.OAuthConsumerAppDTO; import org.wso2.carbon.identity.oauth2.config.models.IssuerDetails; +import org.wso2.carbon.identity.oauth2.fapi.models.FapiProfileEnum; import java.util.ArrayList; import java.util.Arrays; @@ -90,6 +91,11 @@ public OAuthConsumerAppDTO apply(String appName, OpenIDConnectConfiguration oidc updatePARConfigurations(consumerAppDTO, oidcModel.getPushAuthorizationRequest()); updateSubjectConfigurations(consumerAppDTO, oidcModel.getSubject()); consumerAppDTO.setFapiConformanceEnabled(oidcModel.getIsFAPIApplication()); + // Convert FAPI profile enum to String for storage. If no profile specified; default is set to FAPI1_ADVANCED. + if (oidcModel.getIsFAPIApplication()) { + consumerAppDTO.setFapiProfile(oidcModel.getFapiProfile() != null + ? oidcModel.getFapiProfile().toString() : FapiProfileEnum.FAPI1_ADVANCED.value()); + } updateSubjectTokenConfigurations(consumerAppDTO, oidcModel.getSubjectToken()); updateCIBAAuthenticationRequestConfigurations(consumerAppDTO, oidcModel.getCibaAuthenticationRequest()); updateIssuerDetails(consumerAppDTO, oidcModel.getIssuer()); diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/functions/application/inbound/oauth2/OAuthConsumerAppToApiModel.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/functions/application/inbound/oauth2/OAuthConsumerAppToApiModel.java index 78d7f139ac..43e78301a4 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/functions/application/inbound/oauth2/OAuthConsumerAppToApiModel.java +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/core/functions/application/inbound/oauth2/OAuthConsumerAppToApiModel.java @@ -18,10 +18,13 @@ package org.wso2.carbon.identity.api.server.application.management.v1.core.functions.application.inbound.oauth2; import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.wso2.carbon.identity.api.server.application.management.v1.AccessTokenConfiguration; import org.wso2.carbon.identity.api.server.application.management.v1.AllowedIssuer; import org.wso2.carbon.identity.api.server.application.management.v1.CIBAAuthenticationRequestConfiguration; import org.wso2.carbon.identity.api.server.application.management.v1.ClientAuthenticationConfiguration; +import org.wso2.carbon.identity.api.server.application.management.v1.FapiProfile; import org.wso2.carbon.identity.api.server.application.management.v1.HybridFlowConfiguration; import org.wso2.carbon.identity.api.server.application.management.v1.IdTokenConfiguration; import org.wso2.carbon.identity.api.server.application.management.v1.IdTokenEncryptionConfiguration; @@ -48,6 +51,8 @@ */ public class OAuthConsumerAppToApiModel implements Function { + private static final Log LOG = LogFactory.getLog(OAuthConsumerAppToApiModel.class); + @Override public OpenIDConnectConfiguration apply(OAuthConsumerAppDTO oauthAppDTO) { @@ -72,6 +77,7 @@ public OpenIDConnectConfiguration apply(OAuthConsumerAppDTO oauthAppDTO) { .pushAuthorizationRequest(buildPARAuthenticationConfiguration(oauthAppDTO)) .subject(buildSubjectConfiguration(oauthAppDTO)) .isFAPIApplication(oauthAppDTO.isFapiConformanceEnabled()) + .fapiProfile(buildFapiProfileConfiguration(oauthAppDTO)) .subjectToken(buildSubjectTokenConfiguration(oauthAppDTO)) .cibaAuthenticationRequest(buildCIBAAuthenticationRequestConfiguration(oauthAppDTO)) .issuer(buildIssuerOrganizationConfiguration(oauthAppDTO)); @@ -272,4 +278,28 @@ private AllowedIssuer buildIssuerOrganizationConfiguration(OAuthConsumerAppDTO o } return null; } + + /** + * Converts the stored FAPI profile string from the DTO into the API model enum. + * Returns null if no profile is stored, which will omit the field from the response. + * + * @param oauthAppDTO the DTO containing the stored FAPI profile string. + * @return the {@link FapiProfile} enum constant, or null if not set. + */ + private FapiProfile buildFapiProfileConfiguration(OAuthConsumerAppDTO oauthAppDTO) { + + String fapiProfileValue = oauthAppDTO.getFapiProfile(); + if (StringUtils.isBlank(fapiProfileValue)) { + return null; + } + try { + return FapiProfile.fromValue(fapiProfileValue); + } catch (IllegalArgumentException e) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Unrecognised fapiProfile value stored for application '%s': %s", + oauthAppDTO.getApplicationName(), fapiProfileValue), e); + } + return null; + } + } } diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/factories/ServerApplicationMetadataServiceFactory.java b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/factories/ServerApplicationMetadataServiceFactory.java index 6028f95876..536b511c4f 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/factories/ServerApplicationMetadataServiceFactory.java +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/java/org/wso2/carbon/identity/api/server/application/management/v1/factories/ServerApplicationMetadataServiceFactory.java @@ -23,6 +23,7 @@ import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; import org.wso2.carbon.identity.oauth.OAuthAdminServiceImpl; import org.wso2.carbon.identity.oauth.ciba.api.CibaAuthServiceImpl; +import org.wso2.carbon.identity.oauth2.fapi.services.FapiConfigMgtService; import org.wso2.carbon.identity.sso.saml.SAMLSSOConfigServiceImpl; import org.wso2.carbon.security.sts.service.STSAdminServiceInterface; @@ -40,6 +41,7 @@ public class ServerApplicationMetadataServiceFactory { OAuthAdminServiceImpl oAuthAdminService = ApplicationManagementServiceHolder.getOAuthAdminService(); STSAdminServiceInterface sTSAdminServiceInterface = ApplicationManagementServiceHolder.getStsAdminService(); CibaAuthServiceImpl cibaAuthService = ApplicationManagementServiceHolder.getCibaAuthService(); + FapiConfigMgtService fapiConfigMgtService = ApplicationManagementServiceHolder.getFapiConfigMgtService(); if (applicationManagementService == null) { throw new IllegalStateException("ApplicationManagementService is not available from OSGi context."); @@ -56,7 +58,7 @@ public class ServerApplicationMetadataServiceFactory { // Null check for STSAdminServiceInterface is not mandatory as per the previous implementation. SERVICE = new ServerApplicationMetadataService(applicationManagementService, samlSSOConfigService, - oAuthAdminService, sTSAdminServiceInterface, cibaAuthService); + oAuthAdminService, sTSAdminServiceInterface, cibaAuthService, fapiConfigMgtService); } /** diff --git a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/resources/applications.yaml b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/resources/applications.yaml index f64d1751ad..c9d1ec1734 100644 --- a/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/resources/applications.yaml +++ b/components/org.wso2.carbon.identity.api.server.application.management/org.wso2.carbon.identity.api.server.application.management.v1/src/main/resources/applications.yaml @@ -3977,6 +3977,13 @@ components: default: false description: Enabling this option will make the application FAPI conformant. example: false + fapiProfile: + description: | + The FAPI security profile applied to the application. Applicable only when isFAPIApplication is true. + If isFAPIApplication is true and this property is omitted, `FAPI1_ADVANCED` will be applied by default. + allOf: + - $ref: '#/components/schemas/FapiProfile' + example: FAPI1_ADVANCED cibaAuthenticationRequest: $ref: '#/components/schemas/CIBAAuthenticationRequestConfiguration' issuer: @@ -4308,12 +4315,25 @@ components: FapiMetadata: type: object properties: + allowedFapiProfiles: + description: The FAPI security profiles supported by the tenant for this application. + allOf: + - $ref: '#/components/schemas/MetadataProperty' allowedSignatureAlgorithms: $ref: '#/components/schemas/MetadataProperty' allowedEncryptionAlgorithms: $ref: '#/components/schemas/MetadataProperty' tokenEndpointAuthMethod: $ref: '#/components/schemas/ClientAuthenticationMethodMetadata' + FapiProfile: + type: string + description: | + FAPI security profile. + - `FAPI1_ADVANCED`: FAPI 1.0 Advanced Security Profile. + - `FAPI2_SECURITY`: FAPI 2.0 Security Profile. + enum: + - FAPI1_ADVANCED + - FAPI2_SECURITY CIBAMetadata: type: object properties: diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.common/src/main/java/org/wso2/carbon/identity/api/server/configs/common/ConfigsServiceHolder.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.common/src/main/java/org/wso2/carbon/identity/api/server/configs/common/ConfigsServiceHolder.java index 76e236ab6d..762adcdf83 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.common/src/main/java/org/wso2/carbon/identity/api/server/configs/common/ConfigsServiceHolder.java +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.common/src/main/java/org/wso2/carbon/identity/api/server/configs/common/ConfigsServiceHolder.java @@ -29,6 +29,7 @@ import org.wso2.carbon.identity.oauth.dcr.DCRConfigurationMgtService; import org.wso2.carbon.identity.oauth2.agent.services.AgentConfigMgtService; import org.wso2.carbon.identity.oauth2.config.services.OAuth2OIDCConfigOrgUsageScopeMgtService; +import org.wso2.carbon.identity.oauth2.fapi.services.FapiConfigMgtService; import org.wso2.carbon.identity.oauth2.impersonation.services.ImpersonationConfigMgtService; import org.wso2.carbon.identity.oauth2.token.handler.clientauth.jwt.core.JWTClientAuthenticatorMgtService; import org.wso2.carbon.idp.mgt.IdentityProviderManager; @@ -133,6 +134,13 @@ private static class ConsentAppMappingServiceHolder { .getOSGiService(ConsentAppMappingService.class, null); } + private static class FapiConfigMgtServiceHolder { + + static final FapiConfigMgtService SERVICE = + (FapiConfigMgtService) PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getOSGiService(FapiConfigMgtService.class, null); + } + /** * Get ApplicationManagementService osgi service. * @@ -272,4 +280,14 @@ public static ConsentAppMappingService getConsentAppMappingService() { return ConsentAppMappingServiceHolder.SERVICE; } + + /** + * Get FapiConfigMgtService osgi service. + * + * @return FapiConfigMgtService + */ + public static FapiConfigMgtService getFapiConfigMgtService() { + + return FapiConfigMgtServiceHolder.SERVICE; + } } diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.common/src/main/java/org/wso2/carbon/identity/api/server/configs/common/Constants.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.common/src/main/java/org/wso2/carbon/identity/api/server/configs/common/Constants.java index 905e1a477d..6ac4fb2459 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.common/src/main/java/org/wso2/carbon/identity/api/server/configs/common/Constants.java +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.common/src/main/java/org/wso2/carbon/identity/api/server/configs/common/Constants.java @@ -77,6 +77,7 @@ private Constants() { public static final String DCR_CONFIG_SSA_JWKS = "/ssaJwks"; public static final String DCR_CONFIG_AUTHENTICATION_REQUIRED = "/authenticationRequired"; public static final String DCR_CONFIG_MANDATE_SSA = "/mandateSSA"; + public static final String DCR_CONFIG_FAPI_PROFILE = "/fapiProfile"; /** * SAML2 metadata endpoint uri path. @@ -291,7 +292,13 @@ public enum ErrorMessage { "Server encountered an error while mapping application %s."), ERROR_CODE_CONSENT_MAPPING_DELETE("65035", "Unable to delete consent application mapping.", - "Server encountered an error while removing application %s."); + "Server encountered an error while removing application %s."), + ERROR_CODE_FAPI_CONFIG_RETRIEVE("65036", + "Unable to retrieve FAPI configuration.", + "Server encountered an error while retrieving the FAPI configuration."), + ERROR_CODE_FAPI_CONFIG_UPDATE("65037", + "Unable to update FAPI configuration.", + "Server encountered an error while updating the FAPI configuration."); private final String code; private final String message; diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/ConfigsApi.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/ConfigsApi.java index e28dba8a60..92ea7bda19 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/ConfigsApi.java +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/ConfigsApi.java @@ -36,6 +36,7 @@ import org.wso2.carbon.identity.api.server.configs.v1.model.Error; import org.wso2.carbon.identity.api.server.configs.v1.model.AgentConfiguration; import org.wso2.carbon.identity.api.server.configs.v1.model.AgentConfigPatch; +import org.wso2.carbon.identity.api.server.configs.v1.model.FapiConfig; import org.wso2.carbon.identity.api.server.configs.v1.model.FraudDetectionConfig; import org.wso2.carbon.identity.api.server.configs.v1.model.ImpersonationConfiguration; import org.wso2.carbon.identity.api.server.configs.v1.model.ImpersonationPatch; @@ -1169,4 +1170,52 @@ public Response getCompatibilitySettingsByGroup(@ApiParam(value = "Setting group return delegate.getCompatibilitySettingsByGroup(settingGroup); } + + @Valid + @GET + @Path("/fapi") + + @Produces({ "application/json" }) + @ApiOperation(value = "Retrieve the tenant FAPI configuration.", notes = "Retrieve the tenant Financial-grade API (FAPI) configuration.", response = FapiConfig.class, authorizations = { + @Authorization(value = "BasicAuth"), + @Authorization(value = "OAuth2", scopes = { + + }) + }, tags={ "FAPI Configurations", }) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful Response", response = FapiConfig.class), + @ApiResponse(code = 400, message = "Bad Request", response = Error.class), + @ApiResponse(code = 401, message = "Unauthorized", response = Void.class), + @ApiResponse(code = 403, message = "Forbidden", response = Void.class), + @ApiResponse(code = 404, message = "Not Found", response = Error.class), + @ApiResponse(code = 500, message = "Server Error", response = Error.class) + }) + public Response getFAPIConfiguration() { + + return delegate.getFAPIConfiguration(); + } + + @Valid + @PUT + @Path("/fapi") + @Consumes({ "application/json" }) + @Produces({ "application/json" }) + @ApiOperation(value = "Update the tenant FAPI configuration.", notes = "Update the tenant Financial-grade API (FAPI) configuration. Scope (Permission) required:
* internal_config_update ", response = FapiConfig.class, authorizations = { + @Authorization(value = "BasicAuth"), + @Authorization(value = "OAuth2", scopes = { + + }) + }, tags={ "FAPI Configurations", }) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful Response", response = FapiConfig.class), + @ApiResponse(code = 400, message = "Bad Request", response = Error.class), + @ApiResponse(code = 401, message = "Unauthorized", response = Void.class), + @ApiResponse(code = 403, message = "Forbidden", response = Void.class), + @ApiResponse(code = 404, message = "Not Found", response = Error.class), + @ApiResponse(code = 500, message = "Server Error", response = Error.class) + }) + public Response updateFAPIConfiguration(@ApiParam(value = "" ,required=true) @Valid FapiConfig fapiConfig) { + + return delegate.updateFAPIConfiguration(fapiConfig ); + } } diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/ConfigsApiService.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/ConfigsApiService.java index 999b2bd41d..8900d09ee8 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/ConfigsApiService.java +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/ConfigsApiService.java @@ -73,8 +73,11 @@ public interface ConfigsApiService { public Response getPassiveSTSInboundAuthConfig(); public Response getPrivatKeyJWTValidationConfiguration(); + public Response getDCRConfiguration(); + public Response getFAPIConfiguration(); + public Response getRemoteLoggingConfig(String logType); public Response getRemoteLoggingConfigs(); @@ -99,6 +102,8 @@ public interface ConfigsApiService { public Response patchDCRConfiguration(List patch); + public Response updateFAPIConfiguration(FapiConfig fapiConfig); + public Response restoreServerRemoteLoggingConfiguration(String logType); public Response restoreServerRemoteLoggingConfigurations(); diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/DCRConfig.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/DCRConfig.java index 32ed63eeb7..8c1b19e252 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/DCRConfig.java +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/DCRConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-2026, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import org.wso2.carbon.identity.api.server.configs.v1.model.FapiProfile; import javax.validation.constraints.*; @@ -35,6 +36,7 @@ public class DCRConfig { private Boolean authenticationRequired; private String ssaJwks; private Boolean enableFapiEnforcement; + private FapiProfile fapiProfile = null; private Boolean mandateSSA; /** @@ -94,6 +96,25 @@ public void setEnableFapiEnforcement(Boolean enableFapiEnforcement) { this.enableFapiEnforcement = enableFapiEnforcement; } + /** + * The FAPI security profile to enforce for applications created via DCR. Applicable only when enableFapiEnforcement is true. If enableFapiEnforcement is true and this property is omitted, `FAPI1_ADVANCED` will be applied by default. + **/ + public DCRConfig fapiProfile(FapiProfile fapiProfile) { + + this.fapiProfile = fapiProfile; + return this; + } + + @ApiModelProperty(example = "FAPI1_ADVANCED", value = "The FAPI security profile to enforce for applications created via DCR. Applicable only when enableFapiEnforcement is true. If enableFapiEnforcement is true and this property is omitted, `FAPI1_ADVANCED` will be applied by default. ") + @JsonProperty("fapiProfile") + @Valid + public FapiProfile getFapiProfile() { + return fapiProfile; + } + public void setFapiProfile(FapiProfile fapiProfile) { + this.fapiProfile = fapiProfile; + } + /** * If true, the software_statement parameter is mandatory for the DCR create request. **/ @@ -128,12 +149,13 @@ public boolean equals(java.lang.Object o) { return Objects.equals(this.authenticationRequired, dcRConfig.authenticationRequired) && Objects.equals(this.ssaJwks, dcRConfig.ssaJwks) && Objects.equals(this.enableFapiEnforcement, dcRConfig.enableFapiEnforcement) && + Objects.equals(this.fapiProfile, dcRConfig.fapiProfile) && Objects.equals(this.mandateSSA, dcRConfig.mandateSSA); } @Override public int hashCode() { - return Objects.hash(authenticationRequired, ssaJwks, enableFapiEnforcement, mandateSSA); + return Objects.hash(authenticationRequired, ssaJwks, enableFapiEnforcement, fapiProfile, mandateSSA); } @Override @@ -145,6 +167,7 @@ public String toString() { sb.append(" authenticationRequired: ").append(toIndentedString(authenticationRequired)).append("\n"); sb.append(" ssaJwks: ").append(toIndentedString(ssaJwks)).append("\n"); sb.append(" enableFapiEnforcement: ").append(toIndentedString(enableFapiEnforcement)).append("\n"); + sb.append(" fapiProfile: ").append(toIndentedString(fapiProfile)).append("\n"); sb.append(" mandateSSA: ").append(toIndentedString(mandateSSA)).append("\n"); sb.append("}"); return sb.toString(); diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/FapiConfig.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/FapiConfig.java new file mode 100644 index 0000000000..4bae0cd024 --- /dev/null +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/FapiConfig.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.api.server.configs.v1.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.util.ArrayList; +import java.util.List; +import org.wso2.carbon.identity.api.server.configs.v1.model.FapiProfile; +import javax.validation.constraints.*; + +/** + * Financial-grade API (FAPI) configuration for the tenant. + **/ + +import io.swagger.annotations.*; +import java.util.Objects; +import javax.validation.Valid; +import javax.xml.bind.annotation.*; +@ApiModel(description = "Financial-grade API (FAPI) configuration for the tenant.") +public class FapiConfig { + + private Boolean enabled = false; + private List supportedProfiles = null; + + + /** + * Indicates whether FAPI compliance enforcement is enabled for the tenant. + **/ + public FapiConfig enabled(Boolean enabled) { + + this.enabled = enabled; + return this; + } + + @ApiModelProperty(example = "false", value = "Indicates whether FAPI compliance enforcement is enabled for the tenant.") + @JsonProperty("enabled") + @Valid + public Boolean getEnabled() { + return enabled; + } + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + /** + * List of FAPI security profiles supported by the tenant. - `FAPI1_ADVANCED`: FAPI 1.0 Advanced Security Profile (Read and Write API Security Profile). - `FAPI2_SECURITY`: FAPI 2.0 Security Profile (baseline profile for high-security OAuth 2.0 APIs). + **/ + public FapiConfig supportedProfiles(List supportedProfiles) { + + this.supportedProfiles = supportedProfiles; + return this; + } + + @ApiModelProperty(example = "[\"FAPI1_ADVANCED\",\"FAPI2_SECURITY\"]", value = "List of FAPI security profiles supported by the tenant. - `FAPI1_ADVANCED`: FAPI 1.0 Advanced Security Profile (Read and Write API Security Profile). - `FAPI2_SECURITY`: FAPI 2.0 Security Profile (baseline profile for high-security OAuth 2.0 APIs). ") + @JsonProperty("supportedProfiles") + @Valid + public List getSupportedProfiles() { + return supportedProfiles; + } + public void setSupportedProfiles(List supportedProfiles) { + this.supportedProfiles = supportedProfiles; + } + + public FapiConfig addSupportedProfilesItem(FapiProfile supportedProfilesItem) { + if (this.supportedProfiles == null) { + this.supportedProfiles = new ArrayList<>(); + } + this.supportedProfiles.add(supportedProfilesItem); + return this; + } + + + + @Override + public boolean equals(java.lang.Object o) { + + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FapiConfig fapiConfig = (FapiConfig) o; + return Objects.equals(this.enabled, fapiConfig.enabled) && + Objects.equals(this.supportedProfiles, fapiConfig.supportedProfiles); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, supportedProfiles); + } + + @Override + public String toString() { + + StringBuilder sb = new StringBuilder(); + sb.append("class FapiConfig {\n"); + + sb.append(" enabled: ").append(toIndentedString(enabled)).append("\n"); + sb.append(" supportedProfiles: ").append(toIndentedString(supportedProfiles)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} + diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/FapiProfile.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/FapiProfile.java new file mode 100644 index 0000000000..631ead9687 --- /dev/null +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/gen/java/org/wso2/carbon/identity/api/server/configs/v1/model/FapiProfile.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.api.server.configs.v1.model; + +import io.swagger.annotations.ApiModel; +import org.wso2.carbon.identity.oauth2.fapi.models.FapiProfileEnum; + +import javax.validation.constraints.*; + +/** + * FAPI security profile. - `FAPI1_ADVANCED`: FAPI 1.0 Advanced Security Profile. - `FAPI2_SECURITY`: FAPI 2.0 Security Profile. + **/ +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlEnumValue; + +@XmlType(name="") +@XmlEnum(String.class) +public enum FapiProfile { + + @XmlEnumValue("FAPI1_ADVANCED") FAPI1_ADVANCED(String.valueOf("FAPI1_ADVANCED")), @XmlEnumValue("FAPI2_SECURITY") FAPI2_SECURITY(String.valueOf("FAPI2_SECURITY")); + + + private String value; + + FapiProfile(String v) { + value = v; + } + + public String value() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static FapiProfile fromValue(String value) { + + for (FapiProfile b : FapiProfile.values()) { + if (b.value.equals(value)) { + return b; + } + } + return null; + } + + public static FapiProfile fromValue(FapiProfileEnum fapiProfileEnum) { + + if (fapiProfileEnum == null) { + return null; + } + return fromValue(fapiProfileEnum.value()); + } +} diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/core/ServerConfigManagementService.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/core/ServerConfigManagementService.java index 4b5d6f0109..6e6e6337fd 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/core/ServerConfigManagementService.java +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/core/ServerConfigManagementService.java @@ -39,6 +39,7 @@ import org.wso2.carbon.identity.api.server.configs.v1.function.CORSConfigurationToCORSConfig; import org.wso2.carbon.identity.api.server.configs.v1.function.CompatibilitySettingUtil; import org.wso2.carbon.identity.api.server.configs.v1.function.DCRConnectorUtil; +import org.wso2.carbon.identity.api.server.configs.v1.function.FAPIConnectorUtil; import org.wso2.carbon.identity.api.server.configs.v1.function.JWTConnectorUtil; import org.wso2.carbon.identity.api.server.configs.v1.model.AgentConfigPatch; import org.wso2.carbon.identity.api.server.configs.v1.model.AgentConfiguration; @@ -56,6 +57,8 @@ import org.wso2.carbon.identity.api.server.configs.v1.model.Endpoint; import org.wso2.carbon.identity.api.server.configs.v1.model.EventConfig; import org.wso2.carbon.identity.api.server.configs.v1.model.EventProperty; +import org.wso2.carbon.identity.api.server.configs.v1.model.FapiConfig; +import org.wso2.carbon.identity.api.server.configs.v1.model.FapiProfile; import org.wso2.carbon.identity.api.server.configs.v1.model.FraudDetectionConfig; import org.wso2.carbon.identity.api.server.configs.v1.model.ImpersonationConfiguration; import org.wso2.carbon.identity.api.server.configs.v1.model.ImpersonationPatch; @@ -124,6 +127,9 @@ import org.wso2.carbon.identity.oauth2.config.models.IssuerUsageScopeConfig; import org.wso2.carbon.identity.oauth2.config.models.UsageScope; import org.wso2.carbon.identity.oauth2.config.services.OAuth2OIDCConfigOrgUsageScopeMgtService; +import org.wso2.carbon.identity.oauth2.fapi.exceptions.FapiConfigMgtClientException; +import org.wso2.carbon.identity.oauth2.fapi.exceptions.FapiConfigMgtException; +import org.wso2.carbon.identity.oauth2.fapi.services.FapiConfigMgtService; import org.wso2.carbon.identity.oauth2.impersonation.exceptions.ImpersonationConfigMgtClientException; import org.wso2.carbon.identity.oauth2.impersonation.exceptions.ImpersonationConfigMgtException; import org.wso2.carbon.identity.oauth2.impersonation.exceptions.ImpersonationConfigMgtServerException; @@ -184,6 +190,7 @@ public class ServerConfigManagementService { private final DCRConfigurationMgtService dcrConfigurationMgtService; private final OAuth2OIDCConfigOrgUsageScopeMgtService oauth2OIDCConfigOrgUsageScopeMgtService; private final CompatibilitySettingsService compatibilitySettingsService; + private final FapiConfigMgtService fapiConfigMgtService; private static final Log log = LogFactory.getLog(ServerConfigManagementService.class); @@ -198,7 +205,8 @@ public ServerConfigManagementService(ApplicationManagementService applicationMan FraudDetectionConfigsService fraudDetectionConfigsService, OAuth2OIDCConfigOrgUsageScopeMgtService oauth2OIDCConfigOrgUsageScopeMgtService, - CompatibilitySettingsService identityCompatibilitySettingsService) { + CompatibilitySettingsService identityCompatibilitySettingsService, + FapiConfigMgtService fapiConfigMgtService) { this.applicationManagementService = applicationManagementService; this.idpManager = idpManager; @@ -211,6 +219,7 @@ public ServerConfigManagementService(ApplicationManagementService applicationMan this.fraudDetectionConfigsService = fraudDetectionConfigsService; this.oauth2OIDCConfigOrgUsageScopeMgtService = oauth2OIDCConfigOrgUsageScopeMgtService; this.compatibilitySettingsService = identityCompatibilitySettingsService; + this.fapiConfigMgtService = fapiConfigMgtService; } /** @@ -610,6 +619,50 @@ public void deleteAgentConfiguration() { } } + /** + * Get the FAPI configuration for the current tenant. + * + * @return FapiConfig API model. + */ + public FapiConfig getFAPIConfiguration() { + + String tenantDomain = ContextLoader.getTenantDomainFromContext(); + try { + return FAPIConnectorUtil.toApiModel(fapiConfigMgtService.getFapiConfig(tenantDomain)); + } catch (FapiConfigMgtClientException e) { + throw new APIError(Response.Status.BAD_REQUEST, this.getFapiConfigErrorResponse(e, + Constants.ErrorMessage.ERROR_CODE_FAPI_CONFIG_RETRIEVE, tenantDomain)); + } catch (FapiConfigMgtException e) { + throw new APIError(Response.Status.INTERNAL_SERVER_ERROR, this.getFapiConfigErrorResponse(e, + Constants.ErrorMessage.ERROR_CODE_FAPI_CONFIG_RETRIEVE, tenantDomain)); + } + } + + /** + * Update the FAPI configuration for the current tenant. + * + * @param fapiConfig the API model to persist. + * @return the updated FapiConfig API model. + */ + public FapiConfig updateFAPIConfiguration(FapiConfig fapiConfig) { + + if (fapiConfig == null) { + throw handleException(Response.Status.BAD_REQUEST, Constants.ErrorMessage.ERROR_CODE_INVALID_INPUT, + "FAPI configuration is required in the request body."); + } + final String tenantDomain = ContextLoader.getTenantDomainFromContext(); + try { + fapiConfigMgtService.setFapiConfig(FAPIConnectorUtil.toOAuthModel(fapiConfig), tenantDomain); + return FAPIConnectorUtil.toApiModel(fapiConfigMgtService.getFapiConfig(tenantDomain)); + } catch (FapiConfigMgtClientException e) { + throw new APIError(Response.Status.BAD_REQUEST, this.getFapiConfigErrorResponse(e, + Constants.ErrorMessage.ERROR_CODE_FAPI_CONFIG_UPDATE, tenantDomain)); + } catch (FapiConfigMgtException e) { + throw new APIError(Response.Status.INTERNAL_SERVER_ERROR, this.getFapiConfigErrorResponse(e, + Constants.ErrorMessage.ERROR_CODE_FAPI_CONFIG_UPDATE, tenantDomain)); + } + } + /** * Get the CORS config for a tenant. */ @@ -1583,6 +1636,20 @@ private APIError handleAgentConfigException(AgentConfigMgtException e, return new APIError(status, errorResponse); } + private ErrorResponse getFapiConfigErrorResponse(FapiConfigMgtException e, + Constants.ErrorMessage errorEnum, String data) { + + final ErrorResponse errorResponse = getErrorBuilder(errorEnum, data).build(log, e.getMessage()); + errorResponse.setDescription(e.getMessage()); + if (e.getErrorCode() != null) { + String errorCode = e.getErrorCode(); + errorCode = errorCode.contains( + org.wso2.carbon.identity.api.server.common.Constants.ERROR_CODE_DELIMITER) + ? errorCode : Constants.CONFIG_ERROR_PREFIX + errorCode; + errorResponse.setCode(errorCode); + } + return errorResponse; + } /** * Handle exceptions generated in API. @@ -1798,6 +1865,13 @@ public void patchDCRConfig(List dcrPatchList) { } else if (path.matches(Constants.DCR_CONFIG_SSA_JWKS)) { String value = dcrPatch.getValue(); dcrConfig.setSsaJwks(value); + } else if (path.matches(Constants.DCR_CONFIG_FAPI_PROFILE)) { + FapiProfile fapiProfile = FapiProfile.fromValue(dcrPatch.getValue()); + if (fapiProfile == null) { + throw handleException(Response.Status.BAD_REQUEST, Constants.ErrorMessage + .ERROR_CODE_INVALID_INPUT, "Unsupported patch value for the given path"); + } + dcrConfig.setFapiProfile(fapiProfile); } else { // Throw an error if any other patch operations are sent in the request. throw handleException(Response.Status.BAD_REQUEST, Constants.ErrorMessage diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/factories/ServerConfigManagementServiceFactory.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/factories/ServerConfigManagementServiceFactory.java index 765be08778..ffda01c17c 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/factories/ServerConfigManagementServiceFactory.java +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/factories/ServerConfigManagementServiceFactory.java @@ -9,6 +9,7 @@ import org.wso2.carbon.identity.oauth.dcr.DCRConfigurationMgtService; import org.wso2.carbon.identity.oauth2.agent.services.AgentConfigMgtService; import org.wso2.carbon.identity.oauth2.config.services.OAuth2OIDCConfigOrgUsageScopeMgtService; +import org.wso2.carbon.identity.oauth2.fapi.services.FapiConfigMgtService; import org.wso2.carbon.identity.oauth2.impersonation.services.ImpersonationConfigMgtService; import org.wso2.carbon.identity.oauth2.token.handler.clientauth.jwt.core.JWTClientAuthenticatorMgtService; import org.wso2.carbon.idp.mgt.IdentityProviderManager; @@ -41,6 +42,7 @@ public class ServerConfigManagementServiceFactory { ConfigsServiceHolder.getOAuth2OIDCConfigOrgUsageScopeMgtService(); CompatibilitySettingsService compatibilitySettingsService = ConfigsServiceHolder .getIdentityCompatibilitySettingsService(); + FapiConfigMgtService fapiConfigMgtService = ConfigsServiceHolder.getFapiConfigMgtService(); if (applicationManagementService == null) { throw new IllegalStateException("ApplicationManagementService is not available from OSGi context."); @@ -87,6 +89,10 @@ public class ServerConfigManagementServiceFactory { throw new IllegalStateException("CompatibilitySettingsService is not available from OSGi context."); } + if (fapiConfigMgtService == null) { + throw new IllegalStateException("FapiConfigMgtService is not available from OSGi context."); + } + SERVICE = new ServerConfigManagementService(applicationManagementService, identityProviderManager, corsManagementService, remoteLoggingConfigService, @@ -96,7 +102,8 @@ public class ServerConfigManagementServiceFactory { jwtClientAuthenticatorMgtService, fraudDetectionConfigsService, oAuth2OIDCConfigOrgUsageScopeMgtService, - compatibilitySettingsService + compatibilitySettingsService, + fapiConfigMgtService ); } diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/function/DCRConnectorUtil.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/function/DCRConnectorUtil.java index fa55d9de57..38f4d6be65 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/function/DCRConnectorUtil.java +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/function/DCRConnectorUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2024-2026, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -25,11 +25,13 @@ import org.wso2.carbon.identity.api.server.common.error.ErrorResponse; import org.wso2.carbon.identity.api.server.configs.common.Constants; import org.wso2.carbon.identity.api.server.configs.v1.model.DCRConfig; +import org.wso2.carbon.identity.api.server.configs.v1.model.FapiProfile; import org.wso2.carbon.identity.oauth.dcr.DCRConfigurationMgtService; import org.wso2.carbon.identity.oauth.dcr.exception.DCRMClientException; import org.wso2.carbon.identity.oauth.dcr.exception.DCRMException; import org.wso2.carbon.identity.oauth.dcr.exception.DCRMServerException; import org.wso2.carbon.identity.oauth.dcr.model.DCRConfiguration; +import org.wso2.carbon.identity.oauth2.fapi.models.FapiProfileEnum; import javax.ws.rs.core.Response; @@ -72,6 +74,7 @@ private static DCRConfig dcrConfigurationToDCRConfig(DCRConfiguration dcrConfigu dcrConfig.setEnableFapiEnforcement(dcrConfiguration.getEnableFapiEnforcement()); dcrConfig.setSsaJwks(dcrConfiguration.getSsaJwks()); dcrConfig.setMandateSSA(dcrConfiguration.getMandateSSA()); + dcrConfig.setFapiProfile(FapiProfile.fromValue(dcrConfiguration.getFapiProfile())); return dcrConfig; } @@ -166,6 +169,8 @@ private static DCRConfiguration getDCRConfigurationFromDCRConfig (DCRConfig dcrC dcrConfiguration.setSsaJwks(dcrConfig.getSsaJwks()); dcrConfiguration.setAuthenticationRequired(dcrConfig.getAuthenticationRequired()); dcrConfiguration.setMandateSSA(dcrConfig.getMandateSSA()); + dcrConfiguration.setFapiProfile(dcrConfig.getFapiProfile() == null ? + null : FapiProfileEnum.fromValue(dcrConfig.getFapiProfile().value())); return dcrConfiguration; } } diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/function/FAPIConnectorUtil.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/function/FAPIConnectorUtil.java new file mode 100644 index 0000000000..1cae2e2f50 --- /dev/null +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/function/FAPIConnectorUtil.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.api.server.configs.v1.function; + +import org.wso2.carbon.identity.api.server.configs.v1.model.FapiConfig; +import org.wso2.carbon.identity.api.server.configs.v1.model.FapiProfile; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Utility class for converting between the API-layer FAPI model and the OAuth-bundle FAPI model. + */ +public class FAPIConnectorUtil { + + private FAPIConnectorUtil() { + // Utility class — no instantiation. + } + + /** + * Convert an OAuth-bundle {@link org.wso2.carbon.identity.oauth2.fapi.models.FapiConfig} + * to the API-layer {@link FapiConfig}. + * + * @param oauthModel the OAuth bundle model; may be null. + * @return the API model; never null. + */ + public static FapiConfig toApiModel( + org.wso2.carbon.identity.oauth2.fapi.models.FapiConfig oauthModel) { + + FapiConfig apiModel = new FapiConfig(); + if (oauthModel == null) { + apiModel.setEnabled(false); + apiModel.setSupportedProfiles(Collections.emptyList()); + return apiModel; + } + apiModel.setEnabled(oauthModel.isEnabled()); + List oauthProfiles = + oauthModel.getSupportedProfiles(); + if (oauthProfiles == null || oauthProfiles.isEmpty()) { + apiModel.setSupportedProfiles(Collections.emptyList()); + } else { + apiModel.setSupportedProfiles( + oauthProfiles.stream() + .map(p -> FapiProfile.fromValue(p.value())) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + } + return apiModel; + } + + /** + * Convert an API-layer {@link FapiConfig} to the OAuth-bundle + * {@link org.wso2.carbon.identity.oauth2.fapi.models.FapiConfig}. + * + * @param apiModel the API layer model; may be null. + * @return the OAuth model; never null. + */ + public static org.wso2.carbon.identity.oauth2.fapi.models.FapiConfig toOAuthModel(FapiConfig apiModel) { + + org.wso2.carbon.identity.oauth2.fapi.models.FapiConfig oauthModel = + new org.wso2.carbon.identity.oauth2.fapi.models.FapiConfig(); + if (apiModel == null) { + return oauthModel; + } + oauthModel.setEnabled(Boolean.TRUE.equals(apiModel.getEnabled())); + List apiProfiles = apiModel.getSupportedProfiles(); + if (apiProfiles == null || apiProfiles.isEmpty()) { + oauthModel.setSupportedProfiles(Collections.emptyList()); + } else { + oauthModel.setSupportedProfiles( + apiProfiles.stream() + .map(p -> org.wso2.carbon.identity.oauth2.fapi.models.FapiProfileEnum.fromValue( + p.value())) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + } + return oauthModel; + } +} diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/impl/ConfigsApiServiceImpl.java b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/impl/ConfigsApiServiceImpl.java index 8b73a9372e..ad63502c10 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/impl/ConfigsApiServiceImpl.java +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/java/org/wso2/carbon/identity/api/server/configs/v1/impl/ConfigsApiServiceImpl.java @@ -26,6 +26,7 @@ import org.wso2.carbon.identity.api.server.configs.v1.model.CORSPatch; import org.wso2.carbon.identity.api.server.configs.v1.model.CompatibilitySettings; import org.wso2.carbon.identity.api.server.configs.v1.model.DCRPatch; +import org.wso2.carbon.identity.api.server.configs.v1.model.FapiConfig; import org.wso2.carbon.identity.api.server.configs.v1.model.FraudDetectionConfig; import org.wso2.carbon.identity.api.server.configs.v1.model.ImpersonationPatch; import org.wso2.carbon.identity.api.server.configs.v1.model.InboundAuthOAuth2Config; @@ -127,6 +128,12 @@ public Response getDCRConfiguration() { } + @Override + public Response getFAPIConfiguration() { + + return Response.ok().entity(configManagementService.getFAPIConfiguration()).build(); + } + @Override public Response getRemoteLoggingConfig(String logType) { @@ -169,6 +176,12 @@ public Response patchDCRConfiguration(List dcrPatch) { return Response.ok().build(); } + @Override + public Response updateFAPIConfiguration(final FapiConfig fapiConfig) { + + return Response.ok().entity(configManagementService.updateFAPIConfiguration(fapiConfig)).build(); + } + @Override public Response restoreServerRemoteLoggingConfiguration(String logType) { diff --git a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/resources/configs.yaml b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/resources/configs.yaml index 79d41a9615..aa778f5b02 100644 --- a/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/resources/configs.yaml +++ b/components/org.wso2.carbon.identity.api.server.configs/org.wso2.carbon.identity.api.server.configs.v1/src/main/resources/configs.yaml @@ -1497,6 +1497,89 @@ paths: schema: $ref: '#/components/schemas/Error' + /configs/fapi: + get: + tags: + - FAPI Configurations + summary: Retrieve the tenant FAPI configuration. + operationId: getFAPIConfiguration + description: | + Retrieve the tenant Financial-grade API (FAPI) configuration. + Scope (Permission) required: `internal_config_view` + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/FapiConfig' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + put: + tags: + - FAPI Configurations + summary: Put the tenant FAPI configuration. + operationId: putFAPIConfiguration + description: | + Put the tenant Financial-grade API (FAPI) configuration. + Scope (Permission) required:
+ * internal_config_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FapiConfig' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/FapiConfig' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /configs/compatibility-settings: get: tags: @@ -2115,6 +2198,13 @@ components: type: boolean description: If true, a FAPI compliant app will be create with DCR create request. example: true + fapiProfile: + description: | + The FAPI security profile to enforce for applications created via DCR. Applicable only when enableFapiEnforcement is true. + If enableFapiEnforcement is true and this property is omitted, `FAPI1_ADVANCED` will be applied by default. + allOf: + - $ref: '#/components/schemas/FapiProfile' + example: FAPI1_ADVANCED mandateSSA: type: boolean description: If true, the software_statement parameter is mandatory for the DCR create request. @@ -2508,3 +2598,32 @@ components: type: string description: The application resource ID. example: "74070bae-df8c-42bf-8754-5173c237c936" + FapiProfile: + type: string + description: | + FAPI security profile. + - `FAPI1_ADVANCED`: FAPI 1.0 Advanced Security Profile. + - `FAPI2_SECURITY`: FAPI 2.0 Security Profile. + enum: + - FAPI1_ADVANCED + - FAPI2_SECURITY + FapiConfig: + type: object + description: Financial-grade API (FAPI) configuration for the tenant. + properties: + enabled: + type: boolean + description: Indicates whether FAPI compliance enforcement is enabled for the tenant. + example: false + default: false + supportedProfiles: + type: array + description: | + List of FAPI security profiles supported by the tenant. + - `FAPI1_ADVANCED`: FAPI 1.0 Advanced Security Profile (Read and Write API Security Profile). + - `FAPI2_SECURITY`: FAPI 2.0 Security Profile (baseline profile for high-security OAuth 2.0 APIs). + items: + $ref: '#/components/schemas/FapiProfile' + example: + - FAPI1_ADVANCED + - FAPI2_SECURITY