Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move SAMLSSOServiceProviderDO certificate retrieval to SAMLSSOServiceProviderManager layer #6293

Merged
merged 1 commit into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,35 @@
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.database.utils.jdbc.NamedJdbcTemplate;
import org.wso2.carbon.database.utils.jdbc.exceptions.DataAccessException;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.dao.SAMLServiceProviderPersistenceManagerFactory;
import org.wso2.carbon.identity.core.dao.SAMLSSOServiceProviderDAO;
import org.wso2.carbon.identity.core.model.SAMLSSOServiceProviderDO;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.core.util.JdbcUtils;
import org.wso2.carbon.user.api.Tenant;

import java.security.cert.X509Certificate;

import static org.wso2.carbon.identity.core.util.JdbcUtils.isH2DB;

/**
* This class is used for managing SAML SSO providers. Adding, retrieving and removing service
* providers are supported here.
*/
public class SAMLSSOServiceProviderManager {

private static final String CERTIFICATE_PROPERTY_NAME = "CERTIFICATE";
private static final String QUERY_TO_GET_APPLICATION_CERTIFICATE_ID = "SELECT " +
"META.VALUE FROM SP_INBOUND_AUTH INBOUND, SP_APP SP, SP_METADATA META WHERE SP.ID = INBOUND.APP_ID AND " +
"SP.ID = META.SP_ID AND META.NAME = ? AND INBOUND.INBOUND_AUTH_KEY = ? AND META.TENANT_ID = ?";

private static final String QUERY_TO_GET_APPLICATION_CERTIFICATE_ID_H2 = "SELECT " +
"META.`VALUE` FROM SP_INBOUND_AUTH INBOUND, SP_APP SP, SP_METADATA META WHERE SP.ID = INBOUND.APP_ID AND " +
"SP.ID = META.SP_ID AND META.NAME = ? AND INBOUND.INBOUND_AUTH_KEY = ? AND META.TENANT_ID = ?";

SAMLServiceProviderPersistenceManagerFactory samlSSOPersistenceManagerFactory =
new SAMLServiceProviderPersistenceManagerFactory();
SAMLSSOServiceProviderDAO serviceProviderDAO =
Expand All @@ -51,7 +69,7 @@ public boolean addServiceProvider(SAMLSSOServiceProviderDO serviceProviderDO, in

validateServiceProvider(serviceProviderDO);
if (isServiceProviderExists(serviceProviderDO.getIssuer(), tenantId)) {
if (LOG.isDebugEnabled()){
if (LOG.isDebugEnabled()) {
LOG.debug(serviceProviderInfo(serviceProviderDO) + " already exists.");
}
return false;
Expand Down Expand Up @@ -121,10 +139,28 @@ public SAMLSSOServiceProviderDO[] getServiceProviders(int tenantId) throws Ident
*/
public SAMLSSOServiceProviderDO getServiceProvider(String issuer, int tenantId) throws IdentityException {

SAMLSSOServiceProviderDO serviceProviderDO = null;
if (isServiceProviderExists(issuer, tenantId)) {
return serviceProviderDAO.getServiceProvider(issuer, tenantId);
serviceProviderDO = serviceProviderDAO.getServiceProvider(issuer, tenantId);
}
if (serviceProviderDO != null) {
try {
String tenantDomain = IdentityTenantUtil.getTenantDomain(tenantId);
// Load the certificate stored in the database, if signature validation is enabled.
if (serviceProviderDO.isDoValidateSignatureInRequests() ||
serviceProviderDO.isDoValidateSignatureInArtifactResolve() ||
serviceProviderDO.isDoEnableEncryptedAssertion()) {

Tenant tenant = IdentityTenantUtil.getTenant(tenantId);
serviceProviderDO.setX509Certificate(getApplicationCertificate(serviceProviderDO, tenant));
}
serviceProviderDO.setTenantDomain(tenantDomain);
} catch (DataAccessException | CertificateRetrievingException e) {
throw new IdentityException(String.format("An error occurred while getting the " +
"application certificate for validating the requests from the issuer '%s'", issuer), e);
}
}
return null;
return serviceProviderDO;
}

/**
Expand Down Expand Up @@ -173,7 +209,7 @@ public boolean removeServiceProvider(String issuer, int tenantId) throws Identit
* Upload the SAML configuration related to the application, using metadata.
*
* @param serviceProviderDO SAML service provider information object.
* @param tenantId Tenant ID.
* @param tenantId Tenant ID.
* @return SAML service provider information object.
* @throws IdentityException Error when uploading the SAML configuration.
*/
Expand All @@ -186,14 +222,15 @@ public SAMLSSOServiceProviderDO uploadServiceProvider(SAMLSSOServiceProviderDO s
serviceProviderDO.getIssuer());
}
if (isServiceProviderExists(serviceProviderDO.getIssuer(), tenantId)) {
if (LOG.isDebugEnabled()){
if (LOG.isDebugEnabled()) {
LOG.debug(serviceProviderInfo(serviceProviderDO) + " already exists.");
}
throw new IdentityException("A Service Provider already exists.");
}
try {
SAMLSSOServiceProviderDO uploadedServiceProvider = serviceProviderDAO.uploadServiceProvider(serviceProviderDO, tenantId);
if (!(uploadedServiceProvider==null) && LOG.isDebugEnabled()) {
SAMLSSOServiceProviderDO uploadedServiceProvider =
serviceProviderDAO.uploadServiceProvider(serviceProviderDO, tenantId);
if (!(uploadedServiceProvider == null) && LOG.isDebugEnabled()) {
LOG.debug(serviceProviderInfo(serviceProviderDO) + " uploaded successfully.");
}
return uploadedServiceProvider;
Expand Down Expand Up @@ -249,4 +286,57 @@ private String getIssuerWithoutQualifier(String issuerWithQualifier) {
IdentityRegistryResources.QUALIFIER_ID);
return issuerWithoutQualifier;
}

/**
* Returns the {@link java.security.cert.Certificate} which should used to validate the requests
* for the given service provider.
*
* @param serviceProviderDO service provider information object.
* @param tenant tenant Domain.
* @return The X509 certificate used to validate the requests.
* @throws DataAccessException If an error occurs while retrieving the certificate ID.
* @throws CertificateRetrievingException If an error occurs while retrieving the certificate.
*/
private X509Certificate getApplicationCertificate(SAMLSSOServiceProviderDO serviceProviderDO, Tenant tenant)
throws CertificateRetrievingException, DataAccessException {

// Check whether there is a certificate stored against the service provider (in the database).
int applicationCertificateId = getApplicationCertificateId(serviceProviderDO.getIssuer(), tenant.getId());

CertificateRetriever certificateRetriever;
String certificateIdentifier;
if (applicationCertificateId != -1) {
certificateRetriever = new DatabaseCertificateRetriever();
certificateIdentifier = Integer.toString(applicationCertificateId);
} else {
certificateRetriever = new KeyStoreCertificateRetriever();
certificateIdentifier = serviceProviderDO.getCertAlias();
}

return certificateRetriever.getCertificate(certificateIdentifier, tenant);
}

/**
* Returns the certificate reference ID for the given issuer (Service Provider) if there is one.
*
* @param issuer the issuer of the service provider.
* @param tenantId the tenant ID.
* @return the certificate reference ID, or -1 if no certificate is found.
* @throws DataAccessException if an error occurs while retrieving the certificate ID.
*/
private int getApplicationCertificateId(String issuer, int tenantId) throws DataAccessException {

NamedJdbcTemplate namedJdbcTemplate = JdbcUtils.getNewNamedJdbcTemplate();
String sqlStmt =
isH2DB() ? QUERY_TO_GET_APPLICATION_CERTIFICATE_ID_H2 : QUERY_TO_GET_APPLICATION_CERTIFICATE_ID;
Integer certificateId =
namedJdbcTemplate.fetchSingleRecord(sqlStmt, (resultSet, rowNumber) -> resultSet.getInt(1),
namedPreparedStatement -> {
namedPreparedStatement.setString(1, CERTIFICATE_PROPERTY_NAME);
namedPreparedStatement.setString(2, issuer);
namedPreparedStatement.setInt(3, tenantId);
});

return certificateId != null ? certificateId : -1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,53 +21,24 @@
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.database.utils.jdbc.exceptions.DataAccessException;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.CertificateRetriever;
import org.wso2.carbon.identity.core.CertificateRetrievingException;
import org.wso2.carbon.identity.core.DatabaseCertificateRetriever;
import org.wso2.carbon.identity.core.IdentityRegistryResources;
import org.wso2.carbon.identity.core.KeyStoreCertificateRetriever;
import org.wso2.carbon.identity.core.model.SAMLSSOServiceProviderDO;
import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.jdbc.utils.Transaction;
import org.wso2.carbon.registry.core.session.UserRegistry;
import org.wso2.carbon.user.api.Tenant;
import org.wso2.carbon.user.api.UserStoreException;

import java.security.cert.X509Certificate;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

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

import static org.wso2.carbon.identity.core.util.JdbcUtils.isH2DB;

/**
* This class is used for managing SAML SSO service providers in the Registry.
*/
public class RegistrySAMLSSOServiceProviderDAOImpl implements SAMLSSOServiceProviderDAO {

private static final String CERTIFICATE_PROPERTY_NAME = "CERTIFICATE";
private static final String QUERY_TO_GET_APPLICATION_CERTIFICATE_ID = "SELECT " +
"META.VALUE FROM SP_INBOUND_AUTH INBOUND, SP_APP SP, SP_METADATA META WHERE SP.ID = INBOUND.APP_ID AND " +
"SP.ID = META.SP_ID AND META.NAME = ? AND INBOUND.INBOUND_AUTH_KEY = ? AND META.TENANT_ID = ?";

private static final String QUERY_TO_GET_APPLICATION_CERTIFICATE_ID_H2 = "SELECT " +
"META.`VALUE` FROM SP_INBOUND_AUTH INBOUND, SP_APP SP, SP_METADATA META WHERE SP.ID = INBOUND.APP_ID AND " +
"SP.ID = META.SP_ID AND META.NAME = ? AND INBOUND.INBOUND_AUTH_KEY = ? AND META.TENANT_ID = ?";

private static Log LOG = LogFactory.getLog(RegistrySAMLSSOServiceProviderDAOImpl.class);

public RegistrySAMLSSOServiceProviderDAOImpl() {

}
Expand Down Expand Up @@ -408,19 +379,6 @@ private Resource createResource(SAMLSSOServiceProviderDO serviceProviderDO, Regi
return resource;
}

/**
* Get the issuer value by removing the qualifier.
*
* @param issuerWithQualifier issuer value saved in the registry.
* @return issuer value given as 'issuer' when configuring SAML SP.
*/
private String getIssuerWithoutQualifier(String issuerWithQualifier) {

String issuerWithoutQualifier = StringUtils.substringBeforeLast(issuerWithQualifier,
IdentityRegistryResources.QUALIFIER_ID);
return issuerWithoutQualifier;
}

@Override
public boolean updateServiceProvider(SAMLSSOServiceProviderDO serviceProviderDO, String currentIssuer, int tenantId)
throws IdentityException {
Expand Down Expand Up @@ -495,7 +453,7 @@ public boolean removeServiceProvider(String issuer, int tenantId) throws Identit
return true;
} catch (RegistryException e) {
isErrorOccurred = true;
throw new IdentityException("Error while adding SAML Service Provider.", e);
throw new IdentityException("Error while removing SAML Service Provider.", e);
} finally {
commitOrRollbackTransaction(isErrorOccurred, registry);
}
Expand All @@ -507,105 +465,15 @@ public SAMLSSOServiceProviderDO getServiceProvider(String issuer, int tenantId)
Registry registry = getRegistry(tenantId);
String path = IdentityRegistryResources.SAML_SSO_SERVICE_PROVIDERS + encodePath(issuer);
SAMLSSOServiceProviderDO serviceProviderDO = null;

UserRegistry userRegistry = null;
String tenantDomain = null;
try {
userRegistry = (UserRegistry) registry;
tenantDomain = IdentityTenantUtil.getRealmService().getTenantManager().getDomain(userRegistry.
getTenantId());
serviceProviderDO = buildSAMLSSOServiceProviderDAO(registry.get(path));

// Load the certificate stored in the database, if signature validation is enabled..
if (serviceProviderDO.isDoValidateSignatureInRequests() ||
serviceProviderDO.isDoValidateSignatureInArtifactResolve() ||
serviceProviderDO.isDoEnableEncryptedAssertion()) {
Tenant tenant = new Tenant();
tenant.setDomain(tenantDomain);
tenant.setId(userRegistry.getTenantId());

serviceProviderDO.setX509Certificate(getApplicationCertificate(serviceProviderDO, tenant));
}
serviceProviderDO.setTenantDomain(tenantDomain);

} catch (RegistryException e) {
throw IdentityException.error("Error occurred while checking if resource path \'" + path + "\' exists in " +
"registry for tenant domain : " + tenantDomain, e);
} catch (UserStoreException e) {
throw IdentityException.error("Error occurred while getting tenant domain from tenant ID : " +
userRegistry.getTenantId(), e);
} catch (SQLException e) {
throw IdentityException.error(String.format("An error occurred while getting the " +
"application certificate id for validating the requests from the issuer '%s'", issuer), e);
} catch (CertificateRetrievingException e) {
throw IdentityException.error(String.format("An error occurred while getting the " +
"application certificate for validating the requests from the issuer '%s'", issuer), e);
throw IdentityException.error(
"Error occurred while checking if resource path \'" + path + "\' exists in " + "registry", e);
}
return serviceProviderDO;
}

/**
* Returns the {@link java.security.cert.Certificate} which should used to validate the requests
* for the given service provider.
*
* @param serviceProviderDO
* @param tenant
* @return
* @throws SQLException
* @throws CertificateRetrievingException
*/
private X509Certificate getApplicationCertificate(SAMLSSOServiceProviderDO serviceProviderDO, Tenant tenant)
throws SQLException, CertificateRetrievingException {

// Check whether there is a certificate stored against the service provider (in the database)
int applicationCertificateId = getApplicationCertificateId(serviceProviderDO.getIssuer(), tenant.getId());

CertificateRetriever certificateRetriever;
String certificateIdentifier;
if (applicationCertificateId != -1) {
certificateRetriever = new DatabaseCertificateRetriever();
certificateIdentifier = Integer.toString(applicationCertificateId);
} else {
certificateRetriever = new KeyStoreCertificateRetriever();
certificateIdentifier = serviceProviderDO.getCertAlias();
}

return certificateRetriever.getCertificate(certificateIdentifier, tenant);
}

/**
* Returns the certificate reference ID for the given issuer (Service Provider) if there is one.
*
* @param issuer
* @return
* @throws SQLException
*/
private int getApplicationCertificateId(String issuer, int tenantId) throws SQLException {

try {
String sqlStmt = isH2DB() ? QUERY_TO_GET_APPLICATION_CERTIFICATE_ID_H2 :
QUERY_TO_GET_APPLICATION_CERTIFICATE_ID;
try (Connection connection = IdentityDatabaseUtil.getDBConnection(false);
PreparedStatement statementToGetApplicationCertificate =
connection.prepareStatement(sqlStmt)) {
statementToGetApplicationCertificate.setString(1, CERTIFICATE_PROPERTY_NAME);
statementToGetApplicationCertificate.setString(2, issuer);
statementToGetApplicationCertificate.setInt(3, tenantId);

try (ResultSet queryResults = statementToGetApplicationCertificate.executeQuery()) {
if (queryResults.next()) {
return queryResults.getInt(1);
}
}
}
return -1;
} catch (DataAccessException e) {
String errorMsg = "Error while retrieving application certificate data for issuer: " + issuer +
" and tenant Id: " + tenantId;
throw new SQLException(errorMsg, e);
}
}

@Override
public boolean isServiceProviderExists(String issuer, int tenantId) throws IdentityException {

Expand All @@ -628,25 +496,10 @@ private String encodePath(String path) {
public SAMLSSOServiceProviderDO uploadServiceProvider(SAMLSSOServiceProviderDO serviceProviderDO, int tenantId)
throws IdentityException {

Registry registry = getRegistry(tenantId);
String path = IdentityRegistryResources.SAML_SSO_SERVICE_PROVIDERS + encodePath(serviceProviderDO.getIssuer());

boolean isTransactionStarted = Transaction.isStarted();
boolean isErrorOccurred = false;
try {
if (!isTransactionStarted) {
registry.beginTransaction();
}

Resource resource = createResource(serviceProviderDO, registry);
registry.put(path, resource);
if (addServiceProvider(serviceProviderDO, tenantId)) {
return serviceProviderDO;
} catch (RegistryException e) {
isErrorOccurred = true;
throw IdentityException.error("Error while adding Service Provider.", e);
} finally {
commitOrRollbackTransaction(isErrorOccurred, registry);
}
return null;
}

/**
Expand Down
Loading
Loading