| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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. |
| package org.apache.cloudstack.api.command; |
| |
| import java.io.IOException; |
| import java.io.StringWriter; |
| import java.net.InetAddress; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import javax.inject.Inject; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import javax.servlet.http.HttpSession; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.stream.FactoryConfigurationError; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.stream.StreamResult; |
| |
| import org.apache.cloudstack.api.APICommand; |
| import org.apache.cloudstack.api.ApiErrorCode; |
| import org.apache.cloudstack.api.ApiServerService; |
| import org.apache.cloudstack.api.BaseCmd; |
| import org.apache.cloudstack.api.ServerApiException; |
| import org.apache.cloudstack.api.auth.APIAuthenticationType; |
| import org.apache.cloudstack.api.auth.APIAuthenticator; |
| import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator; |
| import org.apache.cloudstack.api.response.SAMLMetaDataResponse; |
| import org.apache.cloudstack.saml.SAML2AuthManager; |
| import org.apache.cloudstack.saml.SAMLProviderMetadata; |
| import org.apache.cloudstack.utils.security.ParserUtils; |
| import org.apache.log4j.Logger; |
| import org.opensaml.Configuration; |
| import org.opensaml.DefaultBootstrap; |
| import org.opensaml.common.xml.SAMLConstants; |
| import org.opensaml.saml2.core.NameIDType; |
| import org.opensaml.saml2.metadata.AssertionConsumerService; |
| import org.opensaml.saml2.metadata.ContactPerson; |
| import org.opensaml.saml2.metadata.ContactPersonTypeEnumeration; |
| import org.opensaml.saml2.metadata.EmailAddress; |
| import org.opensaml.saml2.metadata.EntityDescriptor; |
| import org.opensaml.saml2.metadata.GivenName; |
| import org.opensaml.saml2.metadata.KeyDescriptor; |
| import org.opensaml.saml2.metadata.LocalizedString; |
| import org.opensaml.saml2.metadata.NameIDFormat; |
| import org.opensaml.saml2.metadata.Organization; |
| import org.opensaml.saml2.metadata.OrganizationName; |
| import org.opensaml.saml2.metadata.OrganizationURL; |
| import org.opensaml.saml2.metadata.SPSSODescriptor; |
| import org.opensaml.saml2.metadata.SingleLogoutService; |
| import org.opensaml.saml2.metadata.impl.AssertionConsumerServiceBuilder; |
| import org.opensaml.saml2.metadata.impl.ContactPersonBuilder; |
| import org.opensaml.saml2.metadata.impl.EmailAddressBuilder; |
| import org.opensaml.saml2.metadata.impl.EntityDescriptorBuilder; |
| import org.opensaml.saml2.metadata.impl.GivenNameBuilder; |
| import org.opensaml.saml2.metadata.impl.KeyDescriptorBuilder; |
| import org.opensaml.saml2.metadata.impl.NameIDFormatBuilder; |
| import org.opensaml.saml2.metadata.impl.OrganizationBuilder; |
| import org.opensaml.saml2.metadata.impl.OrganizationNameBuilder; |
| import org.opensaml.saml2.metadata.impl.OrganizationURLBuilder; |
| import org.opensaml.saml2.metadata.impl.SPSSODescriptorBuilder; |
| import org.opensaml.saml2.metadata.impl.SingleLogoutServiceBuilder; |
| import org.opensaml.xml.ConfigurationException; |
| import org.opensaml.xml.io.Marshaller; |
| import org.opensaml.xml.io.MarshallingException; |
| import org.opensaml.xml.security.SecurityException; |
| import org.opensaml.xml.security.credential.UsageType; |
| import org.opensaml.xml.security.keyinfo.KeyInfoGenerator; |
| import org.opensaml.xml.security.x509.BasicX509Credential; |
| import org.opensaml.xml.security.x509.X509KeyInfoGeneratorFactory; |
| import org.w3c.dom.Document; |
| |
| import com.cloud.api.response.ApiResponseSerializer; |
| import com.cloud.user.Account; |
| import com.cloud.utils.HttpUtils; |
| |
| @APICommand(name = "getSPMetadata", description = "Returns SAML2 CloudStack Service Provider MetaData", responseObject = SAMLMetaDataResponse.class, entityType = {}) |
| public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthenticator { |
| public static final Logger s_logger = Logger.getLogger(GetServiceProviderMetaDataCmd.class.getName()); |
| private static final String s_name = "spmetadataresponse"; |
| |
| @Inject |
| ApiServerService _apiServer; |
| |
| SAML2AuthManager _samlAuthManager; |
| |
| ///////////////////////////////////////////////////// |
| /////////////// API Implementation/////////////////// |
| ///////////////////////////////////////////////////// |
| |
| @Override |
| public String getCommandName() { |
| return s_name; |
| } |
| |
| @Override |
| public long getEntityOwnerId() { |
| return Account.Type.NORMAL.ordinal(); |
| } |
| |
| @Override |
| public void execute() throws ServerApiException { |
| throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication plugin api, cannot be used directly"); |
| } |
| |
| @Override |
| public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException { |
| SAMLMetaDataResponse response = new SAMLMetaDataResponse(); |
| response.setResponseName(getCommandName()); |
| |
| try { |
| DefaultBootstrap.bootstrap(); |
| } catch (ConfigurationException | FactoryConfigurationError e) { |
| s_logger.error("OpenSAML Bootstrapping error: " + e.getMessage()); |
| throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), |
| "OpenSAML Bootstrapping error while creating SP MetaData", |
| params, responseType)); |
| } |
| |
| final SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata(); |
| |
| EntityDescriptor spEntityDescriptor = new EntityDescriptorBuilder().buildObject(); |
| spEntityDescriptor.setEntityID(spMetadata.getEntityId()); |
| |
| SPSSODescriptor spSSODescriptor = new SPSSODescriptorBuilder().buildObject(); |
| spSSODescriptor.setWantAssertionsSigned(true); |
| spSSODescriptor.setAuthnRequestsSigned(true); |
| |
| X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory(); |
| keyInfoGeneratorFactory.setEmitEntityCertificate(true); |
| KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance(); |
| |
| KeyDescriptor signKeyDescriptor = new KeyDescriptorBuilder().buildObject(); |
| signKeyDescriptor.setUse(UsageType.SIGNING); |
| |
| KeyDescriptor encKeyDescriptor = new KeyDescriptorBuilder().buildObject(); |
| encKeyDescriptor.setUse(UsageType.ENCRYPTION); |
| |
| BasicX509Credential signingCredential = new BasicX509Credential(); |
| signingCredential.setEntityCertificate(spMetadata.getSigningCertificate()); |
| |
| BasicX509Credential encryptionCredential = new BasicX509Credential(); |
| encryptionCredential.setEntityCertificate(spMetadata.getEncryptionCertificate()); |
| |
| try { |
| signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(signingCredential)); |
| encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(encryptionCredential)); |
| spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor); |
| spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor); |
| } catch (SecurityException e) { |
| s_logger.warn("Unable to add SP X509 descriptors:" + e.getMessage()); |
| } |
| |
| NameIDFormat nameIDFormat = new NameIDFormatBuilder().buildObject(); |
| nameIDFormat.setFormat(NameIDType.PERSISTENT); |
| spSSODescriptor.getNameIDFormats().add(nameIDFormat); |
| |
| NameIDFormat emailNameIDFormat = new NameIDFormatBuilder().buildObject(); |
| emailNameIDFormat.setFormat(NameIDType.EMAIL); |
| spSSODescriptor.getNameIDFormats().add(emailNameIDFormat); |
| |
| NameIDFormat transientNameIDFormat = new NameIDFormatBuilder().buildObject(); |
| transientNameIDFormat.setFormat(NameIDType.TRANSIENT); |
| spSSODescriptor.getNameIDFormats().add(transientNameIDFormat); |
| |
| AssertionConsumerService assertionConsumerService = new AssertionConsumerServiceBuilder().buildObject(); |
| assertionConsumerService.setIndex(1); |
| assertionConsumerService.setIsDefault(true); |
| assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); |
| assertionConsumerService.setLocation(spMetadata.getSsoUrl()); |
| spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService); |
| |
| AssertionConsumerService assertionConsumerService2 = new AssertionConsumerServiceBuilder().buildObject(); |
| assertionConsumerService2.setIndex(2); |
| assertionConsumerService2.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); |
| assertionConsumerService2.setLocation(spMetadata.getSsoUrl()); |
| spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService2); |
| |
| SingleLogoutService ssoService = new SingleLogoutServiceBuilder().buildObject(); |
| ssoService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); |
| ssoService.setLocation(spMetadata.getSloUrl()); |
| spSSODescriptor.getSingleLogoutServices().add(ssoService); |
| |
| SingleLogoutService ssoService2 = new SingleLogoutServiceBuilder().buildObject(); |
| ssoService2.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); |
| ssoService2.setLocation(spMetadata.getSloUrl()); |
| spSSODescriptor.getSingleLogoutServices().add(ssoService2); |
| |
| spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); |
| spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor); |
| |
| // Add technical contact |
| GivenName givenName = new GivenNameBuilder().buildObject(); |
| givenName.setName(spMetadata.getContactPersonName()); |
| EmailAddress emailAddress = new EmailAddressBuilder().buildObject(); |
| emailAddress.setAddress(spMetadata.getContactPersonEmail()); |
| ContactPerson contactPerson = new ContactPersonBuilder().buildObject(); |
| contactPerson.setType(ContactPersonTypeEnumeration.TECHNICAL); |
| contactPerson.setGivenName(givenName); |
| contactPerson.getEmailAddresses().add(emailAddress); |
| spEntityDescriptor.getContactPersons().add(contactPerson); |
| |
| // Add administrative/support contact |
| GivenName givenNameAdmin = new GivenNameBuilder().buildObject(); |
| givenNameAdmin.setName(spMetadata.getContactPersonName()); |
| EmailAddress emailAddressAdmin = new EmailAddressBuilder().buildObject(); |
| emailAddressAdmin.setAddress(spMetadata.getContactPersonEmail()); |
| ContactPerson contactPersonAdmin = new ContactPersonBuilder().buildObject(); |
| contactPersonAdmin.setType(ContactPersonTypeEnumeration.ADMINISTRATIVE); |
| contactPersonAdmin.setGivenName(givenNameAdmin); |
| contactPersonAdmin.getEmailAddresses().add(emailAddressAdmin); |
| spEntityDescriptor.getContactPersons().add(contactPersonAdmin); |
| |
| Organization organization = new OrganizationBuilder().buildObject(); |
| OrganizationName organizationName = new OrganizationNameBuilder().buildObject(); |
| organizationName.setName(new LocalizedString(spMetadata.getOrganizationName(), Locale.getDefault().getLanguage())); |
| OrganizationURL organizationURL = new OrganizationURLBuilder().buildObject(); |
| organizationURL.setURL(new LocalizedString(spMetadata.getOrganizationUrl(), Locale.getDefault().getLanguage())); |
| organization.getOrganizationNames().add(organizationName); |
| organization.getURLs().add(organizationURL); |
| spEntityDescriptor.setOrganization(organization); |
| |
| StringWriter stringWriter = new StringWriter(); |
| try { |
| DocumentBuilderFactory factory = ParserUtils.getSaferDocumentBuilderFactory(); |
| DocumentBuilder builder = factory.newDocumentBuilder(); |
| Document document = builder.newDocument(); |
| Marshaller out = Configuration.getMarshallerFactory().getMarshaller(spEntityDescriptor); |
| out.marshall(spEntityDescriptor, document); |
| |
| Transformer transformer = ParserUtils.getSaferTransformerFactory().newTransformer(); |
| StreamResult streamResult = new StreamResult(stringWriter); |
| DOMSource source = new DOMSource(document); |
| transformer.transform(source, streamResult); |
| stringWriter.close(); |
| response.setMetadata(stringWriter.toString()); |
| } catch (ParserConfigurationException | IOException | MarshallingException | TransformerException e) { |
| if (responseType.equals(HttpUtils.JSON_CONTENT_TYPE)) { |
| response.setMetadata("Error creating Service Provider MetaData XML: " + e.getMessage()); |
| } else { |
| return "Error creating Service Provider MetaData XML: " + e.getMessage(); |
| } |
| } |
| // For JSON type return serialized response object |
| if (responseType.equals(HttpUtils.RESPONSE_TYPE_JSON)) { |
| return ApiResponseSerializer.toSerializedString(response, responseType); |
| } |
| // For other response types return XML |
| return stringWriter.toString(); |
| } |
| |
| @Override |
| public APIAuthenticationType getAPIType() { |
| return APIAuthenticationType.READONLY_API; |
| } |
| |
| @Override |
| public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) { |
| for (PluggableAPIAuthenticator authManager: authenticators) { |
| if (authManager != null && authManager instanceof SAML2AuthManager) { |
| _samlAuthManager = (SAML2AuthManager) authManager; |
| } |
| } |
| if (_samlAuthManager == null) { |
| s_logger.error("No suitable Pluggable Authentication Manager found for SAML2 getSPMetadata Cmd"); |
| } |
| } |
| } |