blob: 5097bc88f2405c84f469cf8d835150892c077cbd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.syncope.core.logic.saml2;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.helpers.IOUtils;
import org.apache.syncope.common.lib.to.ItemTO;
import org.apache.syncope.common.lib.to.SAML2SP4UIIdPTO;
import org.apache.syncope.common.lib.types.SAML2BindingType;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.config.SAML2Configuration;
import org.springframework.core.io.ByteArrayResource;
import org.apache.syncope.core.persistence.api.entity.SAML2SP4UIIdP;
import org.pac4j.saml.metadata.SAML2IdentityProviderMetadataResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Basic in-memory cache for available {@link SAML2Client} instances.
*/
public class SAML2ClientCache {
protected static final Logger LOG = LoggerFactory.getLogger(SAML2ClientCache.class);
protected static Path METADATA_PATH;
static {
try {
METADATA_PATH = Files.createTempDirectory("saml2sp4ui-").toAbsolutePath();
} catch (IOException e) {
LOG.error("Could not create a temp directory to store metadata files", e);
}
}
public static Optional<String> getSPMetadataPath(final String spEntityID) {
String entityIDPath = StringUtils.replaceChars(
StringUtils.removeStart(StringUtils.removeStart(spEntityID, "https://"), "http://"), ":/", "__");
return Optional.ofNullable(METADATA_PATH).map(path -> path.resolve(entityIDPath).toAbsolutePath().toString());
}
protected final List<SAML2Client> cache = Collections.synchronizedList(new ArrayList<>());
public Optional<SAML2Client> get(final String idpEntityID, final String spEntityID) {
return cache.stream().filter(c -> idpEntityID.equals(c.getIdentityProviderResolvedEntityId())
&& spEntityID.equals(c.getConfiguration().getServiceProviderEntityId())).findFirst();
}
public static SAML2SP4UIIdPTO importMetadata(
final InputStream metadata, final SAML2Configuration cfg) throws IOException {
cfg.setIdentityProviderMetadataResource(new ByteArrayResource(IOUtils.readBytesFromStream(metadata)));
SAML2IdentityProviderMetadataResolver metadataResolver = new SAML2IdentityProviderMetadataResolver(cfg);
metadataResolver.init();
String entityId = metadataResolver.getEntityId();
SAML2SP4UIIdPTO idpTO = new SAML2SP4UIIdPTO();
idpTO.setEntityID(entityId);
idpTO.setName(entityId);
EntityDescriptor entityDescriptor = (EntityDescriptor) metadataResolver.getEntityDescriptorElement();
entityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleSignOnServices().forEach(sso -> {
if (idpTO.getBindingType() == null) {
if (SAML2BindingType.POST.getUri().equals(sso.getBinding())) {
idpTO.setBindingType(SAML2BindingType.POST);
} else if (SAML2BindingType.REDIRECT.getUri().equals(sso.getBinding())) {
idpTO.setBindingType(SAML2BindingType.REDIRECT);
}
}
});
if (idpTO.getBindingType() == null) {
throw new IllegalArgumentException("Neither POST nor REDIRECT artifacts supported by " + entityId);
}
cfg.setAuthnRequestBindingType(idpTO.getBindingType().getUri());
cfg.setResponseBindingType(SAML2BindingType.POST.getUri());
cfg.setSpLogoutRequestBindingType(idpTO.getBindingType().getUri());
cfg.setSpLogoutResponseBindingType(idpTO.getBindingType().getUri());
entityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleLogoutServices().stream().
filter(slo -> SAML2BindingType.POST.getUri().equals(slo.getBinding())
|| SAML2BindingType.REDIRECT.getUri().equals(slo.getBinding())).
findFirst().
ifPresent(slo -> idpTO.setLogoutSupported(true));
idpTO.setMetadata(Base64.getEncoder().encodeToString(metadataResolver.getMetadata().getBytes()));
ItemTO connObjectKeyItem = new ItemTO();
connObjectKeyItem.setIntAttrName("username");
connObjectKeyItem.setExtAttrName(NameID.DEFAULT_ELEMENT_LOCAL_NAME);
idpTO.setConnObjectKeyItem(connObjectKeyItem);
return idpTO;
}
public SAML2Client add(
final SAML2SP4UIIdP idp, final SAML2Configuration cfg, final String spEntityID, final String callbackUrl) {
cfg.setIdentityProviderMetadataResource((new ByteArrayResource(idp.getMetadata())));
cfg.setServiceProviderEntityId(spEntityID);
getSPMetadataPath(spEntityID).ifPresent(cfg::setServiceProviderMetadataResourceFilepath);
SAML2Client saml2Client = new SAML2Client(cfg);
saml2Client.setCallbackUrlResolver(new NoParameterCallbackUrlResolver());
saml2Client.setCallbackUrl(callbackUrl);
saml2Client.init();
cache.add(saml2Client);
return saml2Client;
}
public boolean removeAll(final String idpEntityID) {
return cache.removeIf(c -> idpEntityID.equals(c.getIdentityProviderResolvedEntityId()));
}
}