001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *      Nelson Silva
016 */
017
018package org.nuxeo.ecm.platform.auth.saml;
019
020import org.apache.commons.logging.Log;
021import org.apache.commons.logging.LogFactory;
022import org.nuxeo.ecm.platform.auth.saml.binding.SAMLBinding;
023import org.nuxeo.ecm.platform.auth.saml.key.KeyManager;
024import org.nuxeo.runtime.api.Framework;
025import org.opensaml.common.SAMLObject;
026import org.opensaml.common.xml.SAMLConstants;
027import org.opensaml.saml2.core.NameIDType;
028import org.opensaml.saml2.metadata.AssertionConsumerService;
029import org.opensaml.saml2.metadata.EntityDescriptor;
030import org.opensaml.saml2.metadata.KeyDescriptor;
031import org.opensaml.saml2.metadata.NameIDFormat;
032import org.opensaml.saml2.metadata.SPSSODescriptor;
033import org.opensaml.saml2.metadata.SingleLogoutService;
034import org.opensaml.xml.Configuration;
035import org.opensaml.xml.security.SecurityHelper;
036import org.opensaml.xml.security.credential.Credential;
037import org.opensaml.xml.security.credential.UsageType;
038import org.opensaml.xml.security.keyinfo.KeyInfoGenerator;
039import org.opensaml.xml.signature.KeyInfo;
040
041import javax.xml.namespace.QName;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.Collection;
045import java.util.HashSet;
046import java.util.LinkedList;
047import java.util.List;
048import java.util.Set;
049
050/**
051 * @since 7.3
052 */
053public class SAMLConfiguration {
054
055    protected static final Log log = LogFactory.getLog(SAMLConfiguration.class);
056
057    public static final String ENTITY_ID = "nuxeo.saml2.entityId";
058
059    public static final String LOGIN_BINDINGS = "nuxeo.saml2.loginBindings";
060
061    public static final String AUTHN_REQUESTS_SIGNED = "nuxeo.saml2.authnRequestsSigned";
062
063    public static final String WANT_ASSERTIONS_SIGNED = "nuxeo.saml2.wantAssertionsSigned";
064
065    public static final String BINDING_PREFIX = "urn:oasis:names:tc:SAML:2.0:bindings";
066
067    public static final String DEFAULT_LOGIN_BINDINGS = "HTTP-Redirect,HTTP-POST";
068
069    public static final Collection<String> nameID = Arrays.asList(NameIDType.EMAIL, NameIDType.TRANSIENT,
070        NameIDType.PERSISTENT, NameIDType.UNSPECIFIED, NameIDType.X509_SUBJECT);
071
072    private SAMLConfiguration() {
073
074    }
075
076    public static String getEntityId() {
077        return Framework.getProperty(ENTITY_ID, Framework.getProperty("nuxeo.url"));
078    }
079
080    public static List<String> getLoginBindings() {
081        Set<String> supportedBindings = new HashSet<>();
082        for (SAMLBinding binding : SAMLAuthenticationProvider.bindings) {
083            supportedBindings.add(binding.getBindingURI());
084        }
085        List<String> bindings = new ArrayList<>();
086        String[] suffixes = Framework.getProperty(LOGIN_BINDINGS, DEFAULT_LOGIN_BINDINGS).split(",");
087        for (String sufix : suffixes) {
088            String binding = BINDING_PREFIX + ":" + sufix;
089            if (supportedBindings.contains(binding)) {
090                bindings.add(binding);
091            } else {
092                log.warn("Unknown SAML binding " + binding);
093            }
094        }
095        return bindings;
096    }
097
098    public static boolean getAuthnRequestsSigned() {
099        return Boolean.parseBoolean(Framework.getProperty(AUTHN_REQUESTS_SIGNED));
100    }
101
102    public static boolean getWantAssertionsSigned() {
103        return Boolean.parseBoolean(Framework.getProperty(WANT_ASSERTIONS_SIGNED));
104    }
105
106    /**
107     * Returns the {@link EntityDescriptor} for the Nuxeo Service Provider
108     */
109    public static EntityDescriptor getEntityDescriptor(String baseURL) {
110
111        // Entity Descriptor
112        EntityDescriptor descriptor = build(EntityDescriptor.DEFAULT_ELEMENT_NAME);
113        // descriptor.setID(id);
114        descriptor.setEntityID(getEntityId());
115
116        // SPSSO Descriptor
117        SPSSODescriptor spDescriptor = build(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
118        spDescriptor.setAuthnRequestsSigned(getAuthnRequestsSigned());
119        spDescriptor.setWantAssertionsSigned(getWantAssertionsSigned());
120        spDescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS);
121
122        // Name ID
123        spDescriptor.getNameIDFormats().addAll(buildNameIDFormats(nameID));
124
125        // Generate key info
126        KeyManager keyManager = Framework.getLocalService(KeyManager.class);
127        if (keyManager.getSigningCredential() != null) {
128            spDescriptor.getKeyDescriptors().add(
129                buildKeyDescriptor(UsageType.SIGNING,
130                    generateKeyInfoForCredential(keyManager.getSigningCredential())));
131        }
132        if (keyManager.getEncryptionCredential() != null) {
133            spDescriptor.getKeyDescriptors().add(
134                buildKeyDescriptor(UsageType.ENCRYPTION,
135                    generateKeyInfoForCredential(keyManager.getEncryptionCredential())));
136        }
137        if (keyManager.getTlsCredential() != null) {
138            spDescriptor.getKeyDescriptors().add(
139                buildKeyDescriptor(UsageType.UNSPECIFIED,
140                    generateKeyInfoForCredential(keyManager.getTlsCredential())));
141        }
142
143        // LOGIN
144        int index = 0;
145        for (String binding : getLoginBindings()) {
146            AssertionConsumerService consumer = build(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
147            consumer.setLocation(baseURL);
148            consumer.setBinding(binding);
149            consumer.setIsDefault(index == 0);
150            consumer.setIndex(index++);
151            spDescriptor.getAssertionConsumerServices().add(consumer);
152        }
153
154        // LOGOUT - SAML2_POST_BINDING_URI
155        SingleLogoutService logoutService = build(SingleLogoutService.DEFAULT_ELEMENT_NAME);
156        logoutService.setLocation(baseURL);
157        logoutService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
158        spDescriptor.getSingleLogoutServices().add(logoutService);
159
160        descriptor.getRoleDescriptors().add(spDescriptor);
161
162        return descriptor;
163    }
164
165    private static KeyDescriptor buildKeyDescriptor(UsageType type, KeyInfo key) {
166        KeyDescriptor descriptor = build(KeyDescriptor.DEFAULT_ELEMENT_NAME);
167        descriptor.setUse(type);
168        descriptor.setKeyInfo(key);
169        return descriptor;
170    }
171
172    private static Collection<NameIDFormat> buildNameIDFormats(Collection<String> nameIDs) {
173
174        Collection<NameIDFormat> formats = new LinkedList<>();
175
176        // Populate nameIDs
177        for (String nameIDValue : nameIDs) {
178            NameIDFormat nameID = build(NameIDFormat.DEFAULT_ELEMENT_NAME);
179            nameID.setFormat(nameIDValue);
180            formats.add(nameID);
181        }
182
183        return formats;
184    }
185
186    private static KeyInfo generateKeyInfoForCredential(Credential credential) {
187        try {
188            KeyInfoGenerator keyInfoGenerator = SecurityHelper.getKeyInfoGenerator(credential, null, null);
189            return keyInfoGenerator.generate(credential);
190        } catch (org.opensaml.xml.security.SecurityException e) {
191            log.error("Failed to  generate key info.");
192        }
193        return null;
194    }
195
196    private static <T extends SAMLObject> T build(QName qName) {
197        return (T) Configuration.getBuilderFactory().getBuilder(qName).buildObject(qName);
198    }
199
200}