From efa84703d652977743503366c1712d81a7b7d84c Mon Sep 17 00:00:00 2001 From: Sahan Dilshan <32576163+sahandilshan@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:41:48 +0530 Subject: [PATCH] Add protocol config handler (#412) * Add protocol config handler * Bump framework version * Fix formatting issues --- .../IdentitySAMLSSOConfigService.wsdl | 12 + .../org.wso2.carbon.identity.sso.saml/pom.xml | 8 + .../saml/SAML2InboundAuthConfigHandler.java | 343 ++++++++++++++++++ .../sso/saml/SAMLSSOConfigServiceImpl.java | 70 +++- .../identity/sso/saml/SAMLSSOConstants.java | 1 + .../sso/saml/admin/SAMLSSOConfigAdmin.java | 85 +++-- .../sso/saml/dto/SAML2ProtocolConfigDTO.java | 121 ++++++ .../saml/dto/SAMLSSOServiceProviderDTO.java | 28 +- .../IdentitySAMLSSOServiceComponent.java | 33 +- ...IdentitySAMLSSOServiceComponentHolder.java | 47 ++- .../internal/SAMLApplicationMgtListener.java | 17 +- .../identity/sso/saml/util/SAMLSSOUtil.java | 20 + .../SAML2InboundAuthConfigHandlerTest.java | 229 ++++++++++++ .../saml/admin/SAMLSSOConfigAdminTest.java | 17 +- pom.xml | 9 +- 15 files changed, 958 insertions(+), 82 deletions(-) create mode 100644 components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAML2InboundAuthConfigHandler.java create mode 100644 components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAML2ProtocolConfigDTO.java create mode 100644 components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/SAML2InboundAuthConfigHandlerTest.java diff --git a/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOConfigService.wsdl b/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOConfigService.wsdl index 85e17e33c..98a0a5ced 100644 --- a/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOConfigService.wsdl +++ b/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOConfigService.wsdl @@ -237,6 +237,17 @@ + + + + + + + + + + + @@ -246,6 +257,7 @@ + diff --git a/components/org.wso2.carbon.identity.sso.saml/pom.xml b/components/org.wso2.carbon.identity.sso.saml/pom.xml index 7ccd1c34d..6636dc8c9 100755 --- a/components/org.wso2.carbon.identity.sso.saml/pom.xml +++ b/components/org.wso2.carbon.identity.sso.saml/pom.xml @@ -303,6 +303,14 @@ org.apache.felix.scr.ds-annotations provided + + org.apache.axis2.wso2 + axis2 + + + com.google.code.gson + gson + diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAML2InboundAuthConfigHandler.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAML2InboundAuthConfigHandler.java new file mode 100644 index 000000000..6b2e22bb7 --- /dev/null +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAML2InboundAuthConfigHandler.java @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2024, 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.sso.saml; + +import org.apache.commons.lang.StringUtils; +import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants; +import org.wso2.carbon.identity.application.common.IdentityApplicationManagementClientException; +import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; +import org.wso2.carbon.identity.application.common.model.InboundAuthenticationRequestConfig; +import org.wso2.carbon.identity.application.common.model.Property; +import org.wso2.carbon.identity.application.common.model.ServiceProvider; +import org.wso2.carbon.identity.application.mgt.ApplicationConstants; +import org.wso2.carbon.identity.application.mgt.inbound.dto.InboundProtocolConfigurationDTO; +import org.wso2.carbon.identity.application.mgt.inbound.dto.InboundProtocolsDTO; +import org.wso2.carbon.identity.application.mgt.inbound.protocol.ApplicationInboundAuthConfigHandler; +import org.wso2.carbon.identity.base.IdentityException; +import org.wso2.carbon.identity.core.util.IdentityUtil; +import org.wso2.carbon.identity.sso.saml.dto.SAML2ProtocolConfigDTO; +import org.wso2.carbon.identity.sso.saml.dto.SAMLSSOServiceProviderDTO; +import org.wso2.carbon.identity.sso.saml.exception.IdentitySAML2ClientException; +import org.wso2.carbon.identity.sso.saml.exception.IdentitySAML2SSOException; +import org.wso2.carbon.identity.sso.saml.internal.IdentitySAMLSSOServiceComponentHolder; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Optional; + +import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.StandardInboundProtocols.SAML2; +import static org.wso2.carbon.identity.application.mgt.inbound.InboundFunctions.getInboundAuthKey; + +/** + * SAML2 inbound authentication configuration handler. + */ +public class SAML2InboundAuthConfigHandler implements ApplicationInboundAuthConfigHandler { + + private static final String ATTRIBUTE_CONSUMING_SERVICE_INDEX = "attrConsumServiceIndex"; + + /** + * Checks whether this handler can handle the inbound authentication request. + * + * @param inboundProtocolsDTO Inbound protocols DTO. + * @return True if InboundProtocolDTO contains SAML inbound auth configs. + */ + @Override + public boolean canHandle(InboundProtocolsDTO inboundProtocolsDTO) { + + return inboundProtocolsDTO.getInboundProtocolConfigurationMap().containsKey(SAML2); + } + + /** + * Checks whether this handler can handle the inbound authentication request. + * + * @param protocolName Name of the protocol. + * @return True if the protocolName is "samlsso". + */ + @Override + public boolean canHandle(String protocolName) { + + return StringUtils.containsIgnoreCase(ApplicationConstants.StandardInboundProtocols.SAML2, protocolName); + } + + /** + * Creates the inbound authentication request config from InboundProtocolConfigurationDTO. + * + * @param serviceProvider Service provider. + * @param inboundProtocolsDTO Inbound protocols DTO. + * @return InboundAuthenticationRequestConfig. + * @throws IdentityApplicationManagementException If an error occurs while creating the config. + */ + @Override + public InboundAuthenticationRequestConfig handleConfigCreation(ServiceProvider serviceProvider, + InboundProtocolsDTO inboundProtocolsDTO) + throws IdentityApplicationManagementException { + + SAML2ProtocolConfigDTO saml2ProtocolConfigDTO = getSAML2ProtocolConfigDTO(inboundProtocolsDTO); + try { + return createSAMLInbound(serviceProvider, saml2ProtocolConfigDTO); + } catch (IdentitySAML2ClientException e) { + throw new IdentityApplicationManagementClientException(e.getMessage(), e); + } catch (IdentitySAML2SSOException e) { + throw new IdentityApplicationManagementException(e.getErrorCode(), e.getMessage(), e); + } + } + + /** + * Updates the inbound authentication request config from InboundProtocolConfigurationDTO. + * + * @param serviceProvider Service provider. + * @param inboundProtocolConfigurationDTO Inbound protocol configuration DTO. + * @return InboundAuthenticationRequestConfig. + * @throws IdentityApplicationManagementException If an error occurs while updating the config. + */ + @Override + public InboundAuthenticationRequestConfig handleConfigUpdate( + ServiceProvider serviceProvider, InboundProtocolConfigurationDTO inboundProtocolConfigurationDTO) + throws IdentityApplicationManagementException { + + SAML2ProtocolConfigDTO saml2ProtocolConfigDTO = (SAML2ProtocolConfigDTO) inboundProtocolConfigurationDTO; + try { + return updateSAMLInbound(serviceProvider, saml2ProtocolConfigDTO); + } catch (IdentitySAML2ClientException e) { + throw new IdentityApplicationManagementClientException(e.getErrorCode(), e.getMessage(), e); + } catch (IdentitySAML2SSOException e) { + throw new IdentityApplicationManagementException(e.getErrorCode(), e.getMessage(), e); + } + } + + /** + * Deletes the inbound authentication request config. + * + * @param issuer Issuer of the SAMl2 application. + * @throws IdentityApplicationManagementException If an error occurs while deleting the config. + */ + @Override + public void handleConfigDeletion(String issuer) throws IdentityApplicationManagementException { + + try { + IdentitySAMLSSOServiceComponentHolder.getInstance().getSamlSSOConfigService().removeServiceProvider(issuer, + false); + } catch (IdentityException e) { + throw new IdentityApplicationManagementException(e.getErrorCode(), e.getMessage(), e); + } + } + + /** + * Retrieves the inbound authentication request config. + * + * @param issuer Issuer of the SAMl2 application. + * @return InboundProtocolConfigurationDTO. + * @throws IdentityApplicationManagementException If an error occurs while retrieving the config. + */ + @Override + public InboundProtocolConfigurationDTO handleConfigRetrieval(String issuer) + throws IdentityApplicationManagementException { + + try { + SAML2ProtocolConfigDTO saml2ProtocolConfigDTO = new SAML2ProtocolConfigDTO(); + SAMLSSOServiceProviderDTO samlSSOServiceProviderDTO = IdentitySAMLSSOServiceComponentHolder.getInstance() + .getSamlSSOConfigService().getServiceProvider(issuer); + saml2ProtocolConfigDTO.setManualConfiguration(samlSSOServiceProviderDTO); + return saml2ProtocolConfigDTO; + } catch (IdentityException e) { + throw new IdentityApplicationManagementException(e.getErrorCode(), e.getMessage(), e); + } + } + + private static SAML2ProtocolConfigDTO getSAML2ProtocolConfigDTO(InboundProtocolsDTO inboundProtocolsDTO) { + + InboundProtocolConfigurationDTO inboundProtocolConfigurationDTO = inboundProtocolsDTO + .getInboundProtocolConfigurationMap().get(SAML2); + return (SAML2ProtocolConfigDTO) inboundProtocolConfigurationDTO; + } + + private InboundAuthenticationRequestConfig createSAMLInbound(ServiceProvider serviceProvider, + SAML2ProtocolConfigDTO saml2Configuration) + throws IdentitySAML2SSOException { + + SAMLSSOServiceProviderDTO samlssoServiceProviderDTO = getSamlSsoServiceProviderDTO(saml2Configuration); + + // Set certificate if available. + if (samlssoServiceProviderDTO.getCertificateContent() != null) { + serviceProvider.setCertificateContent(base64Encode(samlssoServiceProviderDTO.getCertificateContent())); + } + + return createInboundAuthenticationRequestConfig(samlssoServiceProviderDTO); + } + + private static SAMLSSOServiceProviderDTO getSamlSsoServiceProviderDTO(SAML2ProtocolConfigDTO saml2ProtocolConfigDTO) + throws IdentitySAML2SSOException { + + SAMLSSOServiceProviderDTO samlManualConfiguration = saml2ProtocolConfigDTO.getManualConfiguration(); + + if (saml2ProtocolConfigDTO.getMetadataFile() != null) { + return createSAMLSpWithMetadataFile(saml2ProtocolConfigDTO.getMetadataFile()); + } else if (saml2ProtocolConfigDTO.getMetadataURL() != null) { + return createSAMLSpWithMetadataUrl(saml2ProtocolConfigDTO.getMetadataURL()); + } else if (samlManualConfiguration != null) { + return createSAMLSpWithManualConfiguration(samlManualConfiguration); + } else { + throw new IdentitySAML2ClientException("Invalid SAML2 Configuration. One of metadataFile, metaDataUrl or " + + "serviceProvider manual configuration needs to be present."); + } + } + + private static SAMLSSOServiceProviderDTO createSAMLSpWithMetadataFile(String encodedMetaFileContent) + throws IdentitySAML2SSOException { + + byte[] metaData = Base64.getDecoder().decode(encodedMetaFileContent.getBytes(StandardCharsets.UTF_8)); + String base64DecodedMetadata = new String(metaData, StandardCharsets.UTF_8); + + return IdentitySAMLSSOServiceComponentHolder.getInstance().getSamlSSOConfigService() + .uploadRPServiceProvider(base64DecodedMetadata, false); + } + + private static SAMLSSOServiceProviderDTO createSAMLSpWithMetadataUrl(String metadataUrl) + throws IdentitySAML2SSOException { + + return IdentitySAMLSSOServiceComponentHolder.getInstance().getSamlSSOConfigService() + .createServiceProviderWithMetadataURL(metadataUrl, false); + } + + private static SAMLSSOServiceProviderDTO createSAMLSpWithManualConfiguration( + SAMLSSOServiceProviderDTO samlssoServiceProviderDTO) throws IdentitySAML2SSOException { + + try { + return IdentitySAMLSSOServiceComponentHolder.getInstance().getSamlSSOConfigService() + .createServiceProvider(samlssoServiceProviderDTO, false); + } catch (IdentityException e) { + throw handleException("Error while creating SAML2 service provider.", e); + } + } + + private static String base64Encode(String content) { + + return new String(Base64.getEncoder().encode(content.getBytes(StandardCharsets.UTF_8)), + (StandardCharsets.UTF_8)); + } + + InboundAuthenticationRequestConfig updateSAMLInbound(ServiceProvider application, + SAML2ProtocolConfigDTO saml2ProtocolConfigDTO) + throws IdentitySAML2SSOException { + + // First we identify whether this is a insert or update. + Optional optionalInboundAuthKey = getInboundAuthKey(application, SAML2); + InboundAuthenticationRequestConfig updatedInbound; + if (optionalInboundAuthKey.isPresent()) { + // This is an update. + SAMLSSOServiceProviderDTO samlssoServiceProviderDTO = updateSamlSSoServiceProviderDTO( + saml2ProtocolConfigDTO, optionalInboundAuthKey.get()); + + // Set certificate if available. + if (samlssoServiceProviderDTO.getCertificateContent() != null) { + application.setCertificateContent(base64Encode(samlssoServiceProviderDTO.getCertificateContent())); + } + updatedInbound = createInboundAuthenticationRequestConfig(samlssoServiceProviderDTO); + } else { + updatedInbound = createSAMLInbound(application, saml2ProtocolConfigDTO); + } + return updatedInbound; + } + + private static SAMLSSOServiceProviderDTO updateSamlSSoServiceProviderDTO( + SAML2ProtocolConfigDTO saml2ProtocolConfigDTO, String currentIssuer) + throws IdentitySAML2SSOException { + + SAMLSSOServiceProviderDTO samlManualConfiguration = saml2ProtocolConfigDTO.getManualConfiguration(); + + if (saml2ProtocolConfigDTO.getMetadataFile() != null) { + return updateSAMLSpWithMetadataFile(saml2ProtocolConfigDTO.getMetadataFile(), currentIssuer); + } else if (saml2ProtocolConfigDTO.getMetadataURL() != null) { + return updateSAMLSpWithMetadataUrl(saml2ProtocolConfigDTO.getMetadataURL(), currentIssuer); + } else if (samlManualConfiguration != null) { + return updateSAMLSpWithManualConfiguration(samlManualConfiguration, currentIssuer); + } else { + throw new IdentitySAML2ClientException("Invalid SAML2 Configuration. One of metadataFile, metaDataUrl or " + + "serviceProvider manual configuration needs to be present."); + } + } + + private static SAMLSSOServiceProviderDTO updateSAMLSpWithMetadataFile(String encodedMetaFileContent, + String currentIssuer) + throws IdentitySAML2SSOException { + + byte[] metaData = Base64.getDecoder().decode(encodedMetaFileContent.getBytes(StandardCharsets.UTF_8)); + String base64DecodedMetadata = new String(metaData, StandardCharsets.UTF_8); + + return IdentitySAMLSSOServiceComponentHolder.getInstance().getSamlSSOConfigService() + .updateRPServiceProviderWithMetadata(base64DecodedMetadata, currentIssuer, false); + } + + private static SAMLSSOServiceProviderDTO updateSAMLSpWithMetadataUrl(String metadataUrl, String currentIssuer) + throws IdentitySAML2SSOException { + + return IdentitySAMLSSOServiceComponentHolder.getInstance().getSamlSSOConfigService() + .updateServiceProviderWithMetadataURL(metadataUrl, currentIssuer, false); + } + + private static SAMLSSOServiceProviderDTO updateSAMLSpWithManualConfiguration( + SAMLSSOServiceProviderDTO samlssoServiceProviderDTO, String currentIssuer) + throws IdentitySAML2SSOException { + try { + return IdentitySAMLSSOServiceComponentHolder.getInstance().getSamlSSOConfigService().updateServiceProvider( + samlssoServiceProviderDTO, currentIssuer, false); + } catch (IdentityException e) { + // The above service always returns exception with error code, error message and cause. + throw handleException(e.getMessage(), e); + } + } + + private static InboundAuthenticationRequestConfig createInboundAuthenticationRequestConfig( + SAMLSSOServiceProviderDTO samlssoServiceProviderDTO) throws IdentitySAML2SSOException { + + InboundAuthenticationRequestConfig samlInbound = new InboundAuthenticationRequestConfig(); + samlInbound.setInboundAuthType(FrameworkConstants.StandardInboundProtocols.SAML2); + samlInbound.setInboundAuthKey(samlssoServiceProviderDTO.getIssuer()); + if (samlssoServiceProviderDTO.isEnableAttributeProfile()) { + Property[] properties = new Property[1]; + Property property = new Property(); + property.setName(ATTRIBUTE_CONSUMING_SERVICE_INDEX); + if (StringUtils.isNotBlank(samlssoServiceProviderDTO.getAttributeConsumingServiceIndex())) { + property.setValue(samlssoServiceProviderDTO.getAttributeConsumingServiceIndex()); + } else { + try { + property.setValue(Integer.toString(IdentityUtil.getRandomInteger())); + } catch (IdentityException e) { + throw handleException(e.getMessage(), e); + } + } + properties[0] = property; + samlInbound.setProperties(properties); + } + samlInbound.setData(samlssoServiceProviderDTO.getAuditLogData()); + return samlInbound; + } + + private static IdentitySAML2SSOException handleException(String message, IdentityException ex) { + + if (ex instanceof IdentitySAML2ClientException) { + return (IdentitySAML2ClientException) ex; + } else if (ex instanceof IdentitySAML2SSOException) { + return (IdentitySAML2SSOException) ex; + } + else { + return new IdentitySAML2SSOException(ex.getErrorCode(), message, ex); + } + } +} diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConfigServiceImpl.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConfigServiceImpl.java index 3e81e9cae..6c9e8ff58 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConfigServiceImpl.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConfigServiceImpl.java @@ -116,9 +116,14 @@ public boolean updateRPServiceProvider(SAMLSSOServiceProviderDTO serviceProvider * @throws IdentityException */ public SAMLSSOServiceProviderDTO createServiceProvider(SAMLSSOServiceProviderDTO spDto) throws IdentityException { - + + return createServiceProvider(spDto, true); + } + + SAMLSSOServiceProviderDTO createServiceProvider(SAMLSSOServiceProviderDTO spDto, boolean enableAuditing) throws IdentityException { + validateSAMLSSOServiceProviderDTO(spDto); - SAMLSSOConfigAdmin configAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry()); + SAMLSSOConfigAdmin configAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry(), enableAuditing); try { return configAdmin.addSAMLServiceProvider(spDto); } catch (IdentityException ex) { @@ -137,8 +142,15 @@ public SAMLSSOServiceProviderDTO createServiceProvider(SAMLSSOServiceProviderDTO public SAMLSSOServiceProviderDTO updateServiceProvider(SAMLSSOServiceProviderDTO serviceProviderDTO, String currentIssuer) throws IdentityException { + return updateServiceProvider(serviceProviderDTO, currentIssuer, true); + } + + SAMLSSOServiceProviderDTO updateServiceProvider(SAMLSSOServiceProviderDTO serviceProviderDTO, String currentIssuer, + boolean enableAuditing) + throws IdentityException { + validateSAMLSSOServiceProviderDTO(serviceProviderDTO); - SAMLSSOConfigAdmin configAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry()); + SAMLSSOConfigAdmin configAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry(), enableAuditing); try { return configAdmin.updateSAMLServiceProvider(serviceProviderDTO, currentIssuer); } catch (IdentityException ex) { @@ -179,8 +191,14 @@ private void validateSAMLSSOServiceProviderDTO(SAMLSSOServiceProviderDTO service public SAMLSSOServiceProviderDTO uploadRPServiceProvider(String metadata) throws IdentitySAML2SSOException { + return uploadRPServiceProvider(metadata, true); + } + + SAMLSSOServiceProviderDTO uploadRPServiceProvider(String metadata, boolean enableAuditing) + throws IdentitySAML2SSOException { + try { - SAMLSSOConfigAdmin configAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry()); + SAMLSSOConfigAdmin configAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry(), enableAuditing); if (log.isDebugEnabled()) { log.debug("Creating SAML Service Provider with metadata: " + metadata); } @@ -202,8 +220,15 @@ public SAMLSSOServiceProviderDTO uploadRPServiceProvider(String metadata) throws public SAMLSSOServiceProviderDTO updateRPServiceProviderWithMetadata(String metadata, String currentIssuer) throws IdentitySAML2SSOException { + return updateRPServiceProviderWithMetadata(metadata, currentIssuer, true); + } + + SAMLSSOServiceProviderDTO updateRPServiceProviderWithMetadata(String metadata, String currentIssuer, + boolean enableAuditing) + throws IdentitySAML2SSOException { + try { - SAMLSSOConfigAdmin configAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry()); + SAMLSSOConfigAdmin configAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry(), enableAuditing); if (log.isDebugEnabled()) { log.debug("Updating SAML Service Provider with metadata: " + metadata); } @@ -222,7 +247,13 @@ public SAMLSSOServiceProviderDTO updateRPServiceProviderWithMetadata(String meta */ public SAMLSSOServiceProviderDTO createServiceProviderWithMetadataURL(String metadataUrl) throws IdentitySAML2SSOException { - + + return createServiceProviderWithMetadataURL(metadataUrl, true); + } + + SAMLSSOServiceProviderDTO createServiceProviderWithMetadataURL(String metadataUrl, boolean enableAuditing) + throws IdentitySAML2SSOException { + try { URL url = new URL(metadataUrl); URLConnection con = url.openConnection(); @@ -230,7 +261,7 @@ public SAMLSSOServiceProviderDTO createServiceProviderWithMetadataURL(String met con.setReadTimeout(getReadTimeoutInMillis()); try (InputStream inputStream = new BoundedInputStream(con.getInputStream(), getMaxSizeInBytes())) { String metadata = IOUtils.toString(inputStream); - return uploadRPServiceProvider(metadata); + return uploadRPServiceProvider(metadata, enableAuditing); } } catch (IOException e) { throw handleIOException(URL_NOT_FOUND, "Non-existing metadata URL for SAML service provider creation in tenantDomain: " @@ -249,6 +280,13 @@ public SAMLSSOServiceProviderDTO createServiceProviderWithMetadataURL(String met public SAMLSSOServiceProviderDTO updateServiceProviderWithMetadataURL(String metadataUrl, String currentIssuer) throws IdentitySAML2SSOException { + return updateServiceProviderWithMetadataURL(metadataUrl, currentIssuer, true); + } + + SAMLSSOServiceProviderDTO updateServiceProviderWithMetadataURL(String metadataUrl, String currentIssuer, + boolean enableAuditing) + throws IdentitySAML2SSOException { + try { URL url = new URL(metadataUrl); URLConnection connection = url.openConnection(); @@ -256,12 +294,12 @@ public SAMLSSOServiceProviderDTO updateServiceProviderWithMetadataURL(String met connection.setReadTimeout(getReadTimeoutInMillis()); try (InputStream inputStream = new BoundedInputStream(connection.getInputStream(), getMaxSizeInBytes())) { String metadata = IOUtils.toString(inputStream); - return updateRPServiceProviderWithMetadata(metadata, currentIssuer); + return updateRPServiceProviderWithMetadata(metadata, currentIssuer, enableAuditing); } } catch (IOException e) { throw handleIOException(URL_NOT_FOUND, "Non-existing metadata URL for SAML service provider creation in tenantDomain: " - + getTenantDomain(), e); + + getTenantDomain(), e); } } @@ -459,8 +497,13 @@ public String getKeyEncryptionAlgorithmURIByConfig() { */ public boolean removeServiceProvider(String issuer) throws IdentityException { + return removeServiceProvider(issuer, true); + } + + boolean removeServiceProvider(String issuer, boolean enableAuditing) throws IdentityException { + try { - SAMLSSOConfigAdmin ssoConfigAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry()); + SAMLSSOConfigAdmin ssoConfigAdmin = new SAMLSSOConfigAdmin(getConfigSystemRegistry(), enableAuditing); return ssoConfigAdmin.removeServiceProvider(issuer); } catch (IdentityException ex) { String msg = "Error removing SAML SP with issuer: " + issuer + " in tenantDomain: " + getTenantDomain(); @@ -557,9 +600,12 @@ private Registry getGovernanceRegistry() { private IdentitySAML2SSOException handleException(String message, IdentityException ex) { setErrorCodeIfNotDefined(ex); - if (ex instanceof IdentitySAML2SSOException) { + if (ex instanceof IdentitySAML2ClientException) { + return (IdentitySAML2ClientException) ex; + } else if (ex instanceof IdentitySAML2SSOException) { return (IdentitySAML2SSOException) ex; - } else { + } + else { return new IdentitySAML2SSOException(ex.getErrorCode(), message, ex); } } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConstants.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConstants.java index f766bac5e..1ab36d2fc 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConstants.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConstants.java @@ -209,6 +209,7 @@ public static class LogConstants { public static final String CREATE_SAML_APPLICATION = "CREATE SAML APPLICATION"; public static final String DELETE_SAML_APPLICATION = "DELETE SAML APPLICATION"; + public static final String UPDATE_SAML_APPLICATION = "UPDATE SAML APPLICATION"; public static final String SAML_INBOUND_SERVICE = "saml-inbound-service"; /** diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdmin.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdmin.java index 0c98ed85f..d6d3f9655 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdmin.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdmin.java @@ -18,8 +18,6 @@ package org.wso2.carbon.identity.sso.saml.admin; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -28,7 +26,6 @@ import org.wso2.carbon.context.CarbonContext; import org.wso2.carbon.core.util.KeyStoreManager; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; -import org.wso2.carbon.identity.application.mgt.ApplicationMgtUtil; import org.wso2.carbon.identity.base.IdentityException; import org.wso2.carbon.identity.core.model.SAMLSSOServiceProviderDO; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; @@ -52,12 +49,12 @@ import java.security.KeyStore; import java.security.cert.CertificateException; -import java.util.HashMap; import java.util.Map; import java.util.Optional; import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.LogConstants.USER; import static org.wso2.carbon.identity.application.mgt.ApplicationConstants.LogConstants.TARGET_APPLICATION; +import static org.wso2.carbon.identity.application.mgt.ApplicationMgtUtil.isEnableV2AuditLogs; import static org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils.triggerAuditLogEvent; import static org.wso2.carbon.identity.sso.saml.Error.CONFLICTING_SAML_ISSUER; import static org.wso2.carbon.identity.sso.saml.Error.INVALID_REQUEST; @@ -73,11 +70,18 @@ public class SAMLSSOConfigAdmin { private static final Log log = LogFactory.getLog(SAMLSSOConfigAdmin.class); private UserRegistry registry; private final int tenantId; + private boolean enableAuditing = true; public SAMLSSOConfigAdmin(Registry userRegistry) { registry = (UserRegistry) userRegistry; tenantId = ((UserRegistry) userRegistry).getTenantId(); } + + public SAMLSSOConfigAdmin(Registry userRegistry, boolean enableAuditing) { + + this(userRegistry); + this.enableAuditing = enableAuditing; + } /** * Add a new service provider @@ -102,14 +106,14 @@ public boolean addRelyingPartyServiceProvider(SAMLSSOServiceProviderDTO serviceP } boolean isSuccess = IdentitySAMLSSOServiceComponentHolder.getInstance().getSAMLSSOServiceProviderManager() .addServiceProvider(serviceProviderDO, tenantId); - if (isSuccess && ApplicationMgtUtil.isLegacyAuditLogsDisabledInAppMgt()) { + if (isSuccess && isEnableV2AuditLogs() && enableAuditing) { Optional initiatorId = getInitiatorId(); if (initiatorId.isPresent()) { AuditLog.AuditLogBuilder auditLogBuilder = new AuditLog.AuditLogBuilder( initiatorId.get(), USER, issuer, TARGET_APPLICATION, SAMLSSOConstants.LogConstants.CREATE_SAML_APPLICATION) - .data(buildSPData(serviceProviderDO)); + .data(SAMLSSOUtil.buildSPData(serviceProviderDO)); triggerAuditLogEvent(auditLogBuilder, true); } else { log.error("Error getting the logged in userId"); @@ -169,14 +173,16 @@ public SAMLSSOServiceProviderDTO addSAMLServiceProvider(SAMLSSOServiceProviderDT throw buildClientException(CONFLICTING_SAML_ISSUER, message); } SAMLSSOServiceProviderDTO samlssoServiceProviderDTO = persistSAMLServiceProvider(serviceProviderDO); - if (ApplicationMgtUtil.isLegacyAuditLogsDisabledInAppMgt()) { + Map spDataMap = SAMLSSOUtil.buildSPData(serviceProviderDO); + samlssoServiceProviderDTO.setAuditLogData(spDataMap); + if (isEnableV2AuditLogs() && enableAuditing) { Optional initiatorId = getInitiatorId(); if (initiatorId.isPresent()) { AuditLog.AuditLogBuilder auditLogBuilder = new AuditLog.AuditLogBuilder( initiatorId.get(), USER, issuer, TARGET_APPLICATION, SAMLSSOConstants.LogConstants.CREATE_SAML_APPLICATION) - .data(buildSPData(serviceProviderDO)); + .data(spDataMap); triggerAuditLogEvent(auditLogBuilder, true); } else { log.error("Error getting the logged in userId"); @@ -191,18 +197,6 @@ public SAMLSSOServiceProviderDTO addSAMLServiceProvider(SAMLSSOServiceProviderDT } } - private static Map buildSPData(SAMLSSOServiceProviderDO app) { - - if (app == null) { - return new HashMap<>(); - } - - Gson gson = new Gson(); - String json = gson.toJson(app); - return gson.fromJson(json, new TypeToken>() { - }.getType()); - } - /** * This method is used to retrieve logged in tenant domain. * @return logged in tenant domain. @@ -252,7 +246,26 @@ public SAMLSSOServiceProviderDTO updateSAMLServiceProvider(SAMLSSOServiceProvide String message = "A Service Provider with the name: " + issuer + " is already loaded from the file system."; throw buildClientException(CONFLICTING_SAML_ISSUER, message); } - return persistSAMLServiceProvider(serviceProviderDO, currentIssuer); + SAMLSSOServiceProviderDTO samlssoServiceProviderDTO = persistSAMLServiceProvider(serviceProviderDO, + currentIssuer); + if (samlssoServiceProviderDTO == null) { + return null; + } + Map spDataMap = SAMLSSOUtil.buildSPData(serviceProviderDO); + samlssoServiceProviderDTO.setAuditLogData(spDataMap); + if (isEnableV2AuditLogs() && enableAuditing) { + Optional initiatorId = getInitiatorId(); + if (initiatorId.isPresent()) { + AuditLog.AuditLogBuilder auditLogBuilder = new AuditLog.AuditLogBuilder( + initiatorId.get(), USER, serviceProviderDO.getIssuer(), TARGET_APPLICATION, + SAMLSSOConstants.LogConstants.UPDATE_SAML_APPLICATION) + .data(spDataMap); + triggerAuditLogEvent(auditLogBuilder, true); + } else { + log.error("Error getting the logged in userId"); + } + } + return samlssoServiceProviderDTO; } private String getIssuerWithQualifier(SAMLSSOServiceProviderDO serviceProviderDO) { @@ -366,14 +379,16 @@ public SAMLSSOServiceProviderDTO uploadRelyingPartyServiceProvider(String metada } } SAMLSSOServiceProviderDTO samlssoServiceProviderDTO = persistSAMLServiceProvider(samlssoServiceProviderDO); - if (ApplicationMgtUtil.isLegacyAuditLogsDisabledInAppMgt()) { + Map spDataMap = SAMLSSOUtil.buildSPData(samlssoServiceProviderDO); + samlssoServiceProviderDTO.setAuditLogData(spDataMap); + if (isEnableV2AuditLogs() && enableAuditing) { Optional initiatorId = getInitiatorId(); if (initiatorId.isPresent()) { AuditLog.AuditLogBuilder auditLogBuilder = new AuditLog.AuditLogBuilder( initiatorId.get(), USER, samlssoServiceProviderDO.getIssuer(), TARGET_APPLICATION, SAMLSSOConstants.LogConstants.CREATE_SAML_APPLICATION) - .data(buildSPData(samlssoServiceProviderDO)); + .data(spDataMap); triggerAuditLogEvent(auditLogBuilder, true); } else { log.error("Error getting the logged in userId"); @@ -410,7 +425,26 @@ public SAMLSSOServiceProviderDTO updateRelyingPartyServiceProviderWithMetadata(S throw new IdentityException("Error occurred while setting certificate and alias", e); } } - return persistSAMLServiceProvider(samlssoServiceProviderDO, currentIssuer); + SAMLSSOServiceProviderDTO samlssoServiceProviderDTO = persistSAMLServiceProvider(samlssoServiceProviderDO, + currentIssuer); + if (samlssoServiceProviderDTO == null) { + return null; + } + Map spDataMap = SAMLSSOUtil.buildSPData(samlssoServiceProviderDO); + samlssoServiceProviderDTO.setAuditLogData(spDataMap); + if (isEnableV2AuditLogs() && enableAuditing) { + Optional initiatorId = getInitiatorId(); + if (initiatorId.isPresent()) { + AuditLog.AuditLogBuilder auditLogBuilder = new AuditLog.AuditLogBuilder( + initiatorId.get(), USER, samlssoServiceProviderDO.getIssuer(), TARGET_APPLICATION, + SAMLSSOConstants.LogConstants.UPDATE_SAML_APPLICATION) + .data(spDataMap); + triggerAuditLogEvent(auditLogBuilder, true); + } else { + log.error("Error getting the logged in userId"); + } + } + return samlssoServiceProviderDTO; } private IdentitySAML2ClientException buildClientException(Error error, String message) { @@ -697,7 +731,7 @@ public boolean removeServiceProvider(String issuer) throws IdentityException { boolean isSuccess = IdentitySAMLSSOServiceComponentHolder.getInstance() .getSAMLSSOServiceProviderManager().removeServiceProvider(issuer, tenantId); if (isSuccess) { - if (ApplicationMgtUtil.isLegacyAuditLogsDisabledInAppMgt()) { + if (isEnableV2AuditLogs() && enableAuditing) { Optional initiatorId = getInitiatorId(); if (initiatorId.isPresent()) { AuditLog.AuditLogBuilder auditLogBuilder = new AuditLog.AuditLogBuilder(initiatorId.get(), @@ -728,5 +762,4 @@ protected String getTenantDomain() { return CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); } - } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAML2ProtocolConfigDTO.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAML2ProtocolConfigDTO.java new file mode 100644 index 000000000..d66da3759 --- /dev/null +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAML2ProtocolConfigDTO.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2024, 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.sso.saml.dto; + +import org.wso2.carbon.identity.application.mgt.ApplicationConstants; +import org.wso2.carbon.identity.application.mgt.inbound.dto.InboundProtocolConfigurationDTO; + +import java.util.Map; + +/** + * SAML2 Inbound Protocol Configuration DTO. + */ +public class SAML2ProtocolConfigDTO implements InboundProtocolConfigurationDTO { + + private SAMLSSOServiceProviderDTO manualConfiguration; + private String metadataFile; + private String metadataURL; + private Map auditLogData; + + public SAMLSSOServiceProviderDTO getManualConfiguration() { + + return manualConfiguration; + } + + /** + * Set manual configuration. + * + * @param manualConfiguration Manual configuration. + */ + public void setManualConfiguration(SAMLSSOServiceProviderDTO manualConfiguration) { + + this.manualConfiguration = manualConfiguration; + } + + /** + * Get metadata file. + * + * @return Metadata file. + */ + public String getMetadataFile() { + + return metadataFile; + } + + /** + * Set metadata file. + * + * @param metadataFile Metadata file. + */ + public void setMetadataFile(String metadataFile) { + + this.metadataFile = metadataFile; + } + + /** + * Get metadata URL. + * + * @return Metadata URL. + */ + public String getMetadataURL() { + + return metadataURL; + } + + /** + * Set metadata URL. + * + * @param metadataURL Metadata URL. + */ + public void setMetadataURL(String metadataURL) { + + this.metadataURL = metadataURL; + } + + /** + * Get audit log data. + * + * @return Audit log data. + */ + public Map getAuditLogData() { + + return auditLogData; + } + + /** + * Set audit log data. + * + * @param auditLogData Audit log data. + */ + public void setAuditLogData(Map auditLogData) { + + this.auditLogData = auditLogData; + } + + /** + * Get protocol name. + * + * @return Protocol name. + */ + @Override + public String fetchProtocolName() { + + return ApplicationConstants.StandardInboundProtocols.SAML2; + } +} diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOServiceProviderDTO.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOServiceProviderDTO.java index 5269bc062..450307d89 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOServiceProviderDTO.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOServiceProviderDTO.java @@ -17,18 +17,22 @@ */ package org.wso2.carbon.identity.sso.saml.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonTypeName; +import org.apache.axis2.databinding.annotation.IgnoreNullElement; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.wso2.carbon.identity.application.common.model.InboundConfigurationProtocol; import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil; import java.io.Serializable; +import java.util.Map; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; /** * This class is used to store the SAML SSO Service Provider related information. @@ -38,7 +42,6 @@ @JsonTypeName("samlssoServiceProviderDTO") public class SAMLSSOServiceProviderDTO extends InboundConfigurationProtocol implements Serializable { - private static final long serialVersionUID = -7633935958583257097L; private String issuer; @@ -90,6 +93,11 @@ public class SAMLSSOServiceProviderDTO extends InboundConfigurationProtocol impl private boolean samlECP; private String idpEntityIDAlias; + @IgnoreNullElement + @XmlTransient + @JsonIgnore + private Map auditLogData; + public void setDoValidateSignatureInArtifactResolve(boolean doValidateSignatureInArtifactResolve) { this.doValidateSignatureInArtifactResolve = doValidateSignatureInArtifactResolve; @@ -511,4 +519,22 @@ public void setIdpEntityIDAlias(String idpEntityIDAlias) { this.idpEntityIDAlias = idpEntityIDAlias; } + + /** + * Get audit log data. + * @return A map of audit log data. + */ + public Map getAuditLogData() { + + return auditLogData; + } + + /** + * Set audit log data. + * @param auditLogData A map of audit log data. + */ + public void setAuditLogData(Map auditLogData) { + + this.auditLogData = auditLogData; + } } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/IdentitySAMLSSOServiceComponent.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/IdentitySAMLSSOServiceComponent.java index 166c6132a..dfa809962 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/IdentitySAMLSSOServiceComponent.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/IdentitySAMLSSOServiceComponent.java @@ -32,12 +32,14 @@ import org.wso2.carbon.base.api.ServerConfigurationService; import org.wso2.carbon.identity.application.authentication.framework.listener.SessionContextMgtListener; import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.application.mgt.inbound.protocol.ApplicationInboundAuthConfigHandler; import org.wso2.carbon.identity.application.mgt.listener.ApplicationMgtListener; import org.wso2.carbon.identity.base.IdentityConstants; import org.wso2.carbon.identity.core.SAMLSSOServiceProviderManager; import org.wso2.carbon.identity.core.util.IdentityCoreInitializedEvent; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.event.handler.AbstractEventHandler; +import org.wso2.carbon.identity.sso.saml.SAML2InboundAuthConfigHandler; import org.wso2.carbon.identity.sso.saml.SAMLInboundSessionContextMgtListener; import org.wso2.carbon.identity.sso.saml.SAMLLogoutHandler; import org.wso2.carbon.identity.sso.saml.SAMLSSOConfigServiceImpl; @@ -130,8 +132,14 @@ protected void activate(ComponentContext ctxt) { SSOServiceProviderConfigManager.getInstance(), null); SAMLSSOConfigServiceImpl samlSSOConfigService = new SAMLSSOConfigServiceImpl(); + IdentitySAMLSSOServiceComponentHolder.getInstance().setSamlSSOConfigService(samlSSOConfigService); ServiceRegistration samlSsoConfigServiceRegistration = ctxt.getBundleContext().registerService(SAMLSSOConfigServiceImpl.class, samlSSOConfigService, null); + SAML2InboundAuthConfigHandler saml2InboundAuthConfigHandler = new SAML2InboundAuthConfigHandler(); + IdentitySAMLSSOServiceComponentHolder.getInstance().setSaml2InboundAuthConfigHandler( + saml2InboundAuthConfigHandler); + ctxt.getBundleContext().registerService(ApplicationInboundAuthConfigHandler.class, + saml2InboundAuthConfigHandler, null); SAMLSSOUtil.setSamlssoConfigService(samlSSOConfigService); if (samlSsoConfigServiceRegistration != null) { @@ -436,31 +444,6 @@ protected void unsetExtensionProcessor(SAMLExtensionProcessor extensionProcessor SAMLSSOUtil.removeExtensionProcessors(extensionProcessor); } - /** - * Add dependency to the ApplicationManagementService. - */ - @Reference( - name = "identity.application.management.service", - service = ApplicationManagementService.class, - cardinality = ReferenceCardinality.MANDATORY, - policy = ReferencePolicy.DYNAMIC, - unbind = "unsetApplicationManagementService" - ) - protected void setApplicationManagementService(ApplicationManagementService applicationManagementService) { - - if (log.isDebugEnabled()) { - log.debug("ApplicationManagementService is available"); - } - } - - protected void unsetApplicationManagementService(ApplicationManagementService applicationManagementService) { - - if (log.isDebugEnabled()) { - log.debug("Unset the ApplicationManagementService"); - } - } - - @Reference( name = "saml.sso.service.provider.manager", service = org.wso2.carbon.identity.core.SAMLSSOServiceProviderManager.class, diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/IdentitySAMLSSOServiceComponentHolder.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/IdentitySAMLSSOServiceComponentHolder.java index b2c109cf2..8f7e0c9b7 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/IdentitySAMLSSOServiceComponentHolder.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/IdentitySAMLSSOServiceComponentHolder.java @@ -18,7 +18,10 @@ package org.wso2.carbon.identity.sso.saml.internal; +import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; import org.wso2.carbon.identity.core.SAMLSSOServiceProviderManager; +import org.wso2.carbon.identity.sso.saml.SAML2InboundAuthConfigHandler; +import org.wso2.carbon.identity.sso.saml.SAMLSSOConfigServiceImpl; /** * Identity SAML SSO Service Component Holder. @@ -26,6 +29,8 @@ public class IdentitySAMLSSOServiceComponentHolder { private SAMLSSOServiceProviderManager samlSSOServiceProviderManager; + private SAMLSSOConfigServiceImpl samlSSOConfigService; + private SAML2InboundAuthConfigHandler saml2InboundAuthConfigHandler; private static final IdentitySAMLSSOServiceComponentHolder instance = new IdentitySAMLSSOServiceComponentHolder(); @@ -57,6 +62,44 @@ public SAMLSSOServiceProviderManager getSAMLSSOServiceProviderManager() { return samlSSOServiceProviderManager; } - - + + /** + * Get SAMLSSOConfigService. + * + * @return SAMLSSOConfigService. + */ + public SAMLSSOConfigServiceImpl getSamlSSOConfigService() { + + return samlSSOConfigService; + } + + /** + * Set SAMLSSOConfigService. + * + * @param samlSSOConfigService SAMLSSOConfigService. + */ + public void setSamlSSOConfigService(SAMLSSOConfigServiceImpl samlSSOConfigService) { + + this.samlSSOConfigService = samlSSOConfigService; + } + + /** + * Get SAML2InboundAuthConfigHandler. + * + * @return SAML2InboundAuthConfigHandler. + */ + public SAML2InboundAuthConfigHandler getSaml2InboundAuthConfigHandler() { + + return saml2InboundAuthConfigHandler; + } + + /** + * Set SAML2InboundAuthConfigHandler. + * + * @param saml2InboundAuthConfigHandler SAML2InboundAuthConfigHandler. + */ + public void setSaml2InboundAuthConfigHandler(SAML2InboundAuthConfigHandler saml2InboundAuthConfigHandler) { + + this.saml2InboundAuthConfigHandler = saml2InboundAuthConfigHandler; + } } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/SAMLApplicationMgtListener.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/SAMLApplicationMgtListener.java index ab9f53a8b..c6fea8075 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/SAMLApplicationMgtListener.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/internal/SAMLApplicationMgtListener.java @@ -77,7 +77,6 @@ public boolean doPreDeleteApplication(String applicationName, .getApplicationExcludingFileBasedSPs(applicationName, tenantDomain); if (sp != null) { - // TODO remove after testing if (log.isDebugEnabled()) { log.debug("Initiating the deletion of SAML inbound data associated with service provider: " + applicationName); @@ -89,10 +88,9 @@ public boolean doPreDeleteApplication(String applicationName, log.debug("Removing SAML inbound data for issuer: " + issuerToBeDeleted + " associated with " + "service provider: " + applicationName + " of tenantDomain: " + tenantDomain); } - SAMLSSOUtil.getSAMLSSOConfigService().removeServiceProvider(issuerToBeDeleted); - // TODO remove after testing - SAMLSSOUtil.getSAMLSSOConfigService().getServiceProvider(issuerToBeDeleted); - } catch (IdentityException e) { + IdentitySAMLSSOServiceComponentHolder.getInstance().getSaml2InboundAuthConfigHandler() + .handleConfigDeletion(issuerToBeDeleted); + } catch (IdentityApplicationManagementException e) { String msg = "Error removing SAML inbound data for issuer: %s associated with " + "service provider: %s of tenantDomain: %s during application delete."; throw new IdentityApplicationManagementException( @@ -100,7 +98,6 @@ public boolean doPreDeleteApplication(String applicationName, } } } - return true; } @@ -122,11 +119,13 @@ private void handleSAMLInboundAssociationRemoval(ServiceProvider sp) throws Iden "issuer: " + storedSAMLIssuer); } try { - SAMLSSOUtil.getSAMLSSOConfigService().removeServiceProvider(storedSAMLIssuer); - } catch (IdentityException e) { + IdentitySAMLSSOServiceComponentHolder.getInstance().getSaml2InboundAuthConfigHandler() + .handleConfigDeletion(storedSAMLIssuer); + } catch (IdentityApplicationManagementException e) { String msg = "Error removing SAML inbound data for issuer: %s associated with " + "service provider with id: %s during application update."; - throw new IdentityApplicationManagementException(String.format(msg, storedSAMLIssuer, appId), e); + throw new IdentityApplicationManagementException(e.getErrorCode(), e.getMessage(), String.format(msg, + storedSAMLIssuer, appId), e); } } } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtil.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtil.java index 3e4ee5fb6..6c0e1dfbe 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtil.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtil.java @@ -17,6 +17,8 @@ */ package org.wso2.carbon.identity.sso.saml.util; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import net.shibboleth.utilities.java.support.codec.Base64Support; import net.shibboleth.utilities.java.support.security.SecureRandomIdentifierGenerationStrategy; import net.shibboleth.utilities.java.support.xml.XMLParserException; @@ -2638,6 +2640,24 @@ public static Optional getUserId(AuthenticatedUser authenticatedUser) { } return Optional.empty(); } + + /** + * Build the JSON object of the SAMLSSOServiceProviderDO and return it as a Map. + * + * @param app SAMLSSOServiceProviderDO object. + * @return Map of of the SAMLSSOServiceProviderDO. + */ + public static Map buildSPData(SAMLSSOServiceProviderDO app) { + + if (app == null) { + return new HashMap<>(); + } + + Gson gson = new Gson(); + String json = gson.toJson(app); + return gson.fromJson(json, new TypeToken>() { + }.getType()); + } private static String getSPQualifier(QueryParamDTO[] queryParamDTOs) { diff --git a/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/SAML2InboundAuthConfigHandlerTest.java b/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/SAML2InboundAuthConfigHandlerTest.java new file mode 100644 index 000000000..e616866ff --- /dev/null +++ b/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/SAML2InboundAuthConfigHandlerTest.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2023, 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.sso.saml; + +import org.apache.axis2.context.ConfigurationContext; +import org.apache.axis2.engine.AxisConfiguration; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.internal.util.reflection.FieldSetter; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.testng.PowerMockTestCase; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants; +import org.wso2.carbon.identity.application.common.model.InboundAuthenticationConfig; +import org.wso2.carbon.identity.application.common.model.InboundAuthenticationRequestConfig; +import org.wso2.carbon.identity.application.common.model.ServiceProvider; +import org.wso2.carbon.identity.application.mgt.inbound.dto.InboundProtocolsDTO; +import org.wso2.carbon.identity.core.internal.IdentityCoreServiceComponent; +import org.wso2.carbon.identity.sso.saml.dto.SAML2ProtocolConfigDTO; +import org.wso2.carbon.identity.sso.saml.dto.SAMLSSOServiceProviderDTO; +import org.wso2.carbon.identity.sso.saml.internal.IdentitySAMLSSOServiceComponentHolder; +import org.wso2.carbon.utils.ConfigurationContextService; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +@PrepareForTest({IdentitySAMLSSOServiceComponentHolder.class, PrivilegedCarbonContext.class}) +public class SAML2InboundAuthConfigHandlerTest extends PowerMockTestCase { + + @Mock + private ConfigurationContext configurationContext; + @Mock + private AxisConfiguration axisConfiguration; + @Mock + private IdentitySAMLSSOServiceComponentHolder samlssoServiceComponentHolder; + @Mock + private SAMLSSOConfigServiceImpl samlssoConfigService; + @InjectMocks + private SAML2InboundAuthConfigHandler saml2InboundAuthConfigHandler; + @Mock + private ServiceProvider application; + + private static final String ISSUER = "Issuer_01"; + private static final String APPLICATION_NAME = "dummyApplication"; + private static final String APPLICATION_RESOURCE_ID = "dummyResourceId"; + private static final String META_DATA_URL = "https://localhost:9443/identity/metadata/saml2"; + + @BeforeMethod + public void setUp() throws Exception { + + MockitoAnnotations.initMocks(this); + System.setProperty("carbon.home", + System.getProperty("user.dir") + File.separator + "src" + File.separator + "test" + + File.separator + "resources"); + + initConfigsAndRealm(); + } + + @Test + public void testCreateInboundSAML2Protocol() throws Exception { + + mockSAMLSSOServiceComponentHolder(); + mockServiceProvider(false); + + InboundProtocolsDTO inboundProtocolsDTO = new InboundProtocolsDTO(); + SAML2ProtocolConfigDTO saml2ProtocolConfigDTO = new SAML2ProtocolConfigDTO(); + + saml2ProtocolConfigDTO.setMetadataURL(META_DATA_URL); + inboundProtocolsDTO.addProtocolConfiguration(saml2ProtocolConfigDTO); + + SAMLSSOServiceProviderDTO updatedSAMLSSOServiceProviderDTO = new SAMLSSOServiceProviderDTO(); + updatedSAMLSSOServiceProviderDTO.setAuditLogData(getDummyMap()); + + when(samlssoConfigService.createServiceProviderWithMetadataURL(eq(META_DATA_URL), eq(false))) + .thenReturn(updatedSAMLSSOServiceProviderDTO); + + // We don't need the service provider object for OAuth protocol creation. + InboundAuthenticationRequestConfig result = saml2InboundAuthConfigHandler.handleConfigCreation(application, + inboundProtocolsDTO); + + // Verify that the OAuthAdminService is called with the correct parameters. + verify(samlssoConfigService, times(0)).createServiceProviderWithMetadataURL(eq(META_DATA_URL)); + verify(samlssoConfigService, times(1)).createServiceProviderWithMetadataURL(eq(META_DATA_URL), eq(false)); + + // Asserting the audit log data is added to the result. + Assert.assertFalse(result.getData().isEmpty()); + Assert.assertEquals(result.getInboundAuthType(), FrameworkConstants.StandardInboundProtocols.SAML2); + } + + @Test + public void testUpdateSAML2Protocol() throws Exception { + + mockPrivilegeCarbonContext(); + mockSAMLSSOServiceComponentHolder(); + mockServiceProvider(true); + + SAMLSSOServiceProviderDTO samlssoServiceProviderDTO = new SAMLSSOServiceProviderDTO(); + samlssoServiceProviderDTO.setAuditLogData(getDummyMap()); + samlssoServiceProviderDTO.setIssuer(ISSUER); + + // Mock behavior when currentClientId is not null, indicating an existing application. + when(samlssoConfigService.updateServiceProviderWithMetadataURL(eq(META_DATA_URL), eq(ISSUER), eq(false))) + .thenReturn(samlssoServiceProviderDTO); + SAML2ProtocolConfigDTO saml2ProtocolConfigDTO = new SAML2ProtocolConfigDTO(); + saml2ProtocolConfigDTO.setMetadataURL(META_DATA_URL); + saml2InboundAuthConfigHandler.handleConfigUpdate(application, saml2ProtocolConfigDTO); + + // Verify that SAML service provider is updated without the audit logs. + verify(samlssoConfigService, times(1)).updateServiceProviderWithMetadataURL(eq(META_DATA_URL), eq(ISSUER), + eq(false)); + verify(samlssoConfigService, times(0)).updateServiceProviderWithMetadataURL(any(), any(), eq(true)); + } + + @Test + public void testUpdateSAML2Protocol_CreateNewApplication() throws Exception { + + mockSAMLSSOServiceComponentHolder(); + mockServiceProvider(false); + + SAMLSSOServiceProviderDTO updatedSAMLServiceProvider = new SAMLSSOServiceProviderDTO(); + updatedSAMLServiceProvider.setIssuer(ISSUER); + updatedSAMLServiceProvider.setAuditLogData(getDummyMap()); + when(samlssoConfigService.createServiceProviderWithMetadataURL(eq(META_DATA_URL), eq(false))) + .thenReturn(updatedSAMLServiceProvider); + + // Mock behavior when currentClientId is null, indicating a new application + SAML2ProtocolConfigDTO saml2ProtocolConfigDTO = new SAML2ProtocolConfigDTO(); + saml2ProtocolConfigDTO.setMetadataURL(META_DATA_URL); + + InboundAuthenticationRequestConfig result = saml2InboundAuthConfigHandler.handleConfigUpdate(application, + saml2ProtocolConfigDTO); + + // Verify that SAML service provider is updated without the audit logs. + verify(samlssoConfigService, times(1)).createServiceProviderWithMetadataURL(eq(META_DATA_URL), eq(false)); + verify(samlssoConfigService, times(0)).createServiceProviderWithMetadataURL(any(), eq(true)); + Assert.assertFalse(result.getData().isEmpty()); + } + + @Test + public void testDeleteSAML2Inbound() throws Exception { + + mockPrivilegeCarbonContext(); + mockSAMLSSOServiceComponentHolder(); + + saml2InboundAuthConfigHandler.handleConfigDeletion(ISSUER); + + // Verify that SAML service provider is deleted without the audit logs. + verify(samlssoConfigService, times(1)).removeServiceProvider(eq(ISSUER), eq(false)); + verify(samlssoConfigService, times(0)).removeServiceProvider(eq(ISSUER), eq(true)); + } + + private void initConfigsAndRealm() throws Exception { + + IdentityCoreServiceComponent identityCoreServiceComponent = new IdentityCoreServiceComponent(); + ConfigurationContextService configurationContextService = new ConfigurationContextService + (configurationContext, null); + FieldSetter.setField(identityCoreServiceComponent, identityCoreServiceComponent.getClass(). + getDeclaredField("configurationContextService"), configurationContextService); + when(configurationContext.getAxisConfiguration()).thenReturn(axisConfiguration); + } + + private void mockSAMLSSOServiceComponentHolder() { + + mockStatic(IdentitySAMLSSOServiceComponentHolder.class); + Mockito.when(IdentitySAMLSSOServiceComponentHolder.getInstance()).thenReturn(samlssoServiceComponentHolder); + when(samlssoServiceComponentHolder.getSamlSSOConfigService()).thenReturn(samlssoConfigService); + } + + private void mockPrivilegeCarbonContext() { + + mockStatic(PrivilegedCarbonContext.class); + PrivilegedCarbonContext privilegedCarbonContext = mock(PrivilegedCarbonContext.class); + when(PrivilegedCarbonContext.getThreadLocalCarbonContext()).thenReturn(privilegedCarbonContext); + } + + private void mockServiceProvider(boolean setInboundAuthConfig) { + + this.application = new ServiceProvider(); + application.setApplicationName(APPLICATION_NAME); + application.setApplicationResourceId(APPLICATION_RESOURCE_ID); + InboundAuthenticationConfig inboundAuthenticationConfig = new InboundAuthenticationConfig(); + if(setInboundAuthConfig) { + InboundAuthenticationRequestConfig inboundAuthConfig = new InboundAuthenticationRequestConfig(); + inboundAuthConfig.setInboundAuthKey(ISSUER); + inboundAuthConfig.setInboundAuthType(FrameworkConstants.StandardInboundProtocols.SAML2); + inboundAuthenticationConfig.setInboundAuthenticationRequestConfigs(new InboundAuthenticationRequestConfig[]{ + inboundAuthConfig + }); + application.setInboundAuthenticationConfig(inboundAuthenticationConfig); + } + } + + private Map getDummyMap() { + + Map dummyMap = new HashMap<>(); + dummyMap.put("issuer", ISSUER); + return dummyMap; + } +} diff --git a/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdminTest.java b/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdminTest.java index 8878151be..26c32f976 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdminTest.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdminTest.java @@ -18,7 +18,6 @@ package org.wso2.carbon.identity.sso.saml.admin; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; @@ -37,20 +36,22 @@ import org.wso2.carbon.identity.sso.saml.dto.SAMLSSOServiceProviderDTO; import org.wso2.carbon.identity.sso.saml.exception.IdentitySAML2ClientException; import org.wso2.carbon.identity.sso.saml.internal.IdentitySAMLSSOServiceComponentHolder; +import org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil; import org.wso2.carbon.registry.core.session.UserRegistry; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; +import java.util.Collections; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.powermock.api.mockito.PowerMockito.*; @PrepareForTest({IdentitySAMLSSOServiceComponentHolder.class, SSOServiceProviderConfigManager.class, - SAMLSSOServiceProviderDO.class, Parser.class, UserRegistry.class, SAMLSSOConfigAdmin.class}) + SAMLSSOServiceProviderDO.class, Parser.class, UserRegistry.class, SAMLSSOConfigAdmin.class, SAMLSSOUtil.class}) @PowerMockIgnore({"javax.xml.*", "org.xml.*", "org.apache.xerces.*", "org.w3c.dom.*"}) public class SAMLSSOConfigAdminTest extends PowerMockTestCase { - @InjectMocks private SAMLSSOConfigAdmin samlssoConfigAdmin; @Mock @@ -61,7 +62,7 @@ public class SAMLSSOConfigAdminTest extends PowerMockTestCase { @Mock IdentitySAMLSSOServiceComponentHolder identitySAMLSSOServiceComponentHolder; - @Mock + @Mock(serializable = true) SAMLSSOServiceProviderDO samlssoServiceProvDO; @Mock @@ -69,10 +70,10 @@ public class SAMLSSOConfigAdminTest extends PowerMockTestCase { @Mock Parser parser; - + @BeforeMethod public void setUp() throws Exception { - + TestUtils.startTenantFlow(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); samlssoConfigAdmin = new SAMLSSOConfigAdmin(userRegistry); mockStatic(IdentitySAMLSSOServiceComponentHolder.class); @@ -152,6 +153,8 @@ public void testCreateSAMLSSOServiceProviderDOWithInvalidIssuerQualifier() throw public void testUploadRelyingPartyServiceProvider() throws Exception { String metadata = "metadata"; + mockStatic(SAMLSSOUtil.class); + when(SAMLSSOUtil.buildSPData(any())).thenReturn(Collections.emptyMap()); when(samlSSOServiceProviderManager.addServiceProvider(any(SAMLSSOServiceProviderDO.class), anyInt())) .thenReturn(true); whenNew(SAMLSSOServiceProviderDO.class).withNoArguments().thenReturn(samlssoServiceProvDO); @@ -192,6 +195,8 @@ public void testUploadRelyingPartyServiceProvider2(String issuer) throws Excepti public void testUpdateRelyingPartyServiceProviderWithMetadata() throws Exception { String metadata = "metadata"; + mockStatic(SAMLSSOUtil.class); + when(SAMLSSOUtil.buildSPData(any())).thenReturn(Collections.emptyMap()); when(samlSSOServiceProviderManager.updateServiceProvider(any(SAMLSSOServiceProviderDO.class), anyString(), anyInt())) .thenReturn(true); whenNew(SAMLSSOServiceProviderDO.class).withNoArguments().thenReturn(samlssoServiceProvDO); diff --git a/pom.xml b/pom.xml index 2bf389185..b6bb6f11f 100644 --- a/pom.xml +++ b/pom.xml @@ -371,6 +371,11 @@ test ${carbon.identity.organization.management.core.version} + + com.google.code.gson + gson + ${com.google.code.gson.version} + @@ -473,9 +478,11 @@ 4.4.14.wso2v1 4.5.13.wso2v1 1.2.0.wso2v1 - 1.6.1.wso2v12 + 1.6.1-wso2v40 2.9.4.wso2v1 2.13.2 + 2.9.0 + [2.3.1,3.0.0) 3.2.0 2.3.1