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