/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed 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. * * Contributors: * Nelson Silva */ package org.nuxeo.ecm.platform.auth.saml; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.platform.auth.saml.binding.SAMLBinding; import org.nuxeo.ecm.platform.auth.saml.key.KeyManager; import org.nuxeo.runtime.api.Framework; import org.opensaml.common.SAMLObject; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.core.NameIDType; import org.opensaml.saml2.metadata.AssertionConsumerService; import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.saml2.metadata.KeyDescriptor; import org.opensaml.saml2.metadata.NameIDFormat; import org.opensaml.saml2.metadata.SPSSODescriptor; import org.opensaml.saml2.metadata.SingleLogoutService; import org.opensaml.xml.Configuration; import org.opensaml.xml.security.SecurityHelper; import org.opensaml.xml.security.credential.Credential; import org.opensaml.xml.security.credential.UsageType; import org.opensaml.xml.security.keyinfo.KeyInfoGenerator; import org.opensaml.xml.signature.KeyInfo; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * @since 7.3 */ public class SAMLConfiguration { protected static final Log log = LogFactory.getLog(SAMLConfiguration.class); public static final String ENTITY_ID = "nuxeo.saml2.entityId"; public static final String LOGIN_BINDINGS = "nuxeo.saml2.loginBindings"; public static final String AUTHN_REQUESTS_SIGNED = "nuxeo.saml2.authnRequestsSigned"; public static final String WANT_ASSERTIONS_SIGNED = "nuxeo.saml2.wantAssertionsSigned"; public static final String BINDING_PREFIX = "urn:oasis:names:tc:SAML:2.0:bindings"; public static final String DEFAULT_LOGIN_BINDINGS = "HTTP-Redirect,HTTP-POST"; public static final Collection<String> nameID = Arrays.asList(NameIDType.EMAIL, NameIDType.TRANSIENT, NameIDType.PERSISTENT, NameIDType.UNSPECIFIED, NameIDType.X509_SUBJECT); private SAMLConfiguration() { } public static String getEntityId() { return Framework.getProperty(ENTITY_ID, Framework.getProperty("nuxeo.url")); } public static List<String> getLoginBindings() { Set<String> supportedBindings = new HashSet<>(); for (SAMLBinding binding : SAMLAuthenticationProvider.bindings) { supportedBindings.add(binding.getBindingURI()); } List<String> bindings = new ArrayList<>(); String[] suffixes = Framework.getProperty(LOGIN_BINDINGS, DEFAULT_LOGIN_BINDINGS).split(","); for (String sufix : suffixes) { String binding = BINDING_PREFIX + ":" + sufix; if (supportedBindings.contains(binding)) { bindings.add(binding); } else { log.warn("Unknown SAML binding " + binding); } } return bindings; } public static boolean getAuthnRequestsSigned() { return Boolean.parseBoolean(Framework.getProperty(AUTHN_REQUESTS_SIGNED)); } public static boolean getWantAssertionsSigned() { return Boolean.parseBoolean(Framework.getProperty(WANT_ASSERTIONS_SIGNED)); } /** * Returns the {@link EntityDescriptor} for the Nuxeo Service Provider */ public static EntityDescriptor getEntityDescriptor(String baseURL) { // Entity Descriptor EntityDescriptor descriptor = build(EntityDescriptor.DEFAULT_ELEMENT_NAME); // descriptor.setID(id); descriptor.setEntityID(getEntityId()); // SPSSO Descriptor SPSSODescriptor spDescriptor = build(SPSSODescriptor.DEFAULT_ELEMENT_NAME); spDescriptor.setAuthnRequestsSigned(getAuthnRequestsSigned()); spDescriptor.setWantAssertionsSigned(getWantAssertionsSigned()); spDescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); // Name ID spDescriptor.getNameIDFormats().addAll(buildNameIDFormats(nameID)); // Generate key info KeyManager keyManager = Framework.getLocalService(KeyManager.class); if (keyManager.getSigningCredential() != null) { spDescriptor.getKeyDescriptors().add( buildKeyDescriptor(UsageType.SIGNING, generateKeyInfoForCredential(keyManager.getSigningCredential()))); } if (keyManager.getEncryptionCredential() != null) { spDescriptor.getKeyDescriptors().add( buildKeyDescriptor(UsageType.ENCRYPTION, generateKeyInfoForCredential(keyManager.getEncryptionCredential()))); } if (keyManager.getTlsCredential() != null) { spDescriptor.getKeyDescriptors().add( buildKeyDescriptor(UsageType.UNSPECIFIED, generateKeyInfoForCredential(keyManager.getTlsCredential()))); } // LOGIN int index = 0; for (String binding : getLoginBindings()) { AssertionConsumerService consumer = build(AssertionConsumerService.DEFAULT_ELEMENT_NAME); consumer.setLocation(baseURL); consumer.setBinding(binding); consumer.setIsDefault(index == 0); consumer.setIndex(index++); spDescriptor.getAssertionConsumerServices().add(consumer); } // LOGOUT - SAML2_POST_BINDING_URI SingleLogoutService logoutService = build(SingleLogoutService.DEFAULT_ELEMENT_NAME); logoutService.setLocation(baseURL); logoutService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); spDescriptor.getSingleLogoutServices().add(logoutService); descriptor.getRoleDescriptors().add(spDescriptor); return descriptor; } private static KeyDescriptor buildKeyDescriptor(UsageType type, KeyInfo key) { KeyDescriptor descriptor = build(KeyDescriptor.DEFAULT_ELEMENT_NAME); descriptor.setUse(type); descriptor.setKeyInfo(key); return descriptor; } private static Collection<NameIDFormat> buildNameIDFormats(Collection<String> nameIDs) { Collection<NameIDFormat> formats = new LinkedList<>(); // Populate nameIDs for (String nameIDValue : nameIDs) { NameIDFormat nameID = build(NameIDFormat.DEFAULT_ELEMENT_NAME); nameID.setFormat(nameIDValue); formats.add(nameID); } return formats; } private static KeyInfo generateKeyInfoForCredential(Credential credential) { try { KeyInfoGenerator keyInfoGenerator = SecurityHelper.getKeyInfoGenerator(credential, null, null); return keyInfoGenerator.generate(credential); } catch (org.opensaml.xml.security.SecurityException e) { log.error("Failed to generate key info."); } return null; } private static <T extends SAMLObject> T build(QName qName) { return (T) Configuration.getBuilderFactory().getBuilder(qName).buildObject(qName); } }