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}