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 SKEW_TIME_MS= "nuxeo.saml2.skewTimeMs"; 068 069 public static final int DEFAULT_SKEW_TIME_MS = 1000 * 60; // 1 minute; 070 071 public static final String BINDING_PREFIX = "urn:oasis:names:tc:SAML:2.0:bindings"; 072 073 public static final String DEFAULT_LOGIN_BINDINGS = "HTTP-Redirect,HTTP-POST"; 074 075 public static final Collection<String> nameID = Arrays.asList(NameIDType.EMAIL, NameIDType.TRANSIENT, 076 NameIDType.PERSISTENT, NameIDType.UNSPECIFIED, NameIDType.X509_SUBJECT); 077 078 private SAMLConfiguration() { 079 080 } 081 082 public static String getEntityId() { 083 return Framework.getProperty(ENTITY_ID, Framework.getProperty("nuxeo.url")); 084 } 085 086 public static List<String> getLoginBindings() { 087 Set<String> supportedBindings = new HashSet<>(); 088 for (SAMLBinding binding : SAMLAuthenticationProvider.bindings) { 089 supportedBindings.add(binding.getBindingURI()); 090 } 091 List<String> bindings = new ArrayList<>(); 092 String[] suffixes = Framework.getProperty(LOGIN_BINDINGS, DEFAULT_LOGIN_BINDINGS).split(","); 093 for (String sufix : suffixes) { 094 String binding = BINDING_PREFIX + ":" + sufix; 095 if (supportedBindings.contains(binding)) { 096 bindings.add(binding); 097 } else { 098 log.warn("Unknown SAML binding " + binding); 099 } 100 } 101 return bindings; 102 } 103 104 public static boolean getAuthnRequestsSigned() { 105 return Boolean.parseBoolean(Framework.getProperty(AUTHN_REQUESTS_SIGNED)); 106 } 107 108 public static boolean getWantAssertionsSigned() { 109 return Boolean.parseBoolean(Framework.getProperty(WANT_ASSERTIONS_SIGNED)); 110 } 111 112 public static int getSkewTimeMillis() { 113 String skewTimeMs = Framework.getProperty(SKEW_TIME_MS); 114 return skewTimeMs != null ? Integer.parseInt(skewTimeMs) : DEFAULT_SKEW_TIME_MS; 115 } 116 117 /** 118 * Returns the {@link EntityDescriptor} for the Nuxeo Service Provider 119 */ 120 public static EntityDescriptor getEntityDescriptor(String baseURL) { 121 122 // Entity Descriptor 123 EntityDescriptor descriptor = build(EntityDescriptor.DEFAULT_ELEMENT_NAME); 124 // descriptor.setID(id); 125 descriptor.setEntityID(getEntityId()); 126 127 // SPSSO Descriptor 128 descriptor.getRoleDescriptors().add(getSPSSODescriptor(baseURL)); 129 130 return descriptor; 131 } 132 133 /** 134 * Returns the {@link SPSSODescriptor} for the Nuxeo Service Provider 135 */ 136 public static SPSSODescriptor getSPSSODescriptor(String baseURL) { 137 SPSSODescriptor spDescriptor = build(SPSSODescriptor.DEFAULT_ELEMENT_NAME); 138 spDescriptor.setAuthnRequestsSigned(getAuthnRequestsSigned()); 139 spDescriptor.setWantAssertionsSigned(getWantAssertionsSigned()); 140 spDescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); 141 142 // Name ID 143 spDescriptor.getNameIDFormats().addAll(buildNameIDFormats(nameID)); 144 145 // Generate key info 146 KeyManager keyManager = Framework.getService(KeyManager.class); 147 if (keyManager.getSigningCredential() != null) { 148 spDescriptor.getKeyDescriptors().add( 149 buildKeyDescriptor(UsageType.SIGNING, 150 generateKeyInfoForCredential(keyManager.getSigningCredential()))); 151 } 152 if (keyManager.getEncryptionCredential() != null) { 153 spDescriptor.getKeyDescriptors().add( 154 buildKeyDescriptor(UsageType.ENCRYPTION, 155 generateKeyInfoForCredential(keyManager.getEncryptionCredential()))); 156 } 157 if (keyManager.getTlsCredential() != null) { 158 spDescriptor.getKeyDescriptors().add( 159 buildKeyDescriptor(UsageType.UNSPECIFIED, 160 generateKeyInfoForCredential(keyManager.getTlsCredential()))); 161 } 162 163 // LOGIN 164 int index = 0; 165 for (String binding : getLoginBindings()) { 166 AssertionConsumerService consumer = build(AssertionConsumerService.DEFAULT_ELEMENT_NAME); 167 consumer.setLocation(baseURL); 168 consumer.setBinding(binding); 169 consumer.setIsDefault(index == 0); 170 consumer.setIndex(index++); 171 spDescriptor.getAssertionConsumerServices().add(consumer); 172 } 173 174 // LOGOUT - SAML2_POST_BINDING_URI 175 SingleLogoutService logoutService = build(SingleLogoutService.DEFAULT_ELEMENT_NAME); 176 logoutService.setLocation(baseURL); 177 logoutService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); 178 spDescriptor.getSingleLogoutServices().add(logoutService); 179 return spDescriptor; 180 } 181 182 private static KeyDescriptor buildKeyDescriptor(UsageType type, KeyInfo key) { 183 KeyDescriptor descriptor = build(KeyDescriptor.DEFAULT_ELEMENT_NAME); 184 descriptor.setUse(type); 185 descriptor.setKeyInfo(key); 186 return descriptor; 187 } 188 189 private static Collection<NameIDFormat> buildNameIDFormats(Collection<String> nameIDs) { 190 191 Collection<NameIDFormat> formats = new LinkedList<>(); 192 193 // Populate nameIDs 194 for (String nameIDValue : nameIDs) { 195 NameIDFormat nameID = build(NameIDFormat.DEFAULT_ELEMENT_NAME); 196 nameID.setFormat(nameIDValue); 197 formats.add(nameID); 198 } 199 200 return formats; 201 } 202 203 private static KeyInfo generateKeyInfoForCredential(Credential credential) { 204 try { 205 KeyInfoGenerator keyInfoGenerator = SecurityHelper.getKeyInfoGenerator(credential, null, null); 206 return keyInfoGenerator.generate(credential); 207 } catch (org.opensaml.xml.security.SecurityException e) { 208 log.error("Failed to generate key info."); 209 } 210 return null; 211 } 212 213 private static <T extends SAMLObject> T build(QName qName) { 214 return (T) Configuration.getBuilderFactory().getBuilder(qName).buildObject(qName); 215 } 216 217}