saml2: Add GetServiceProviderMetaDataCmd that returns SP metadata XML This adds GetServiceProviderMetaDataCmd which returns SP metadata XML, since this information should be public for IdPs to discover, we implement this as a login/cmd api so this does not require any kind of authentication to GET this
Signed-off-by: Rohit Yadav <rohit.ya...@shapeblue.com> Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/99ae373b Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/99ae373b Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/99ae373b Branch: refs/heads/saml2 Commit: 99ae373b9eb7dbe74e4802ab6e3bb8b2b2f60c1a Parents: b84b2c1 Author: Rohit Yadav <rohit.ya...@shapeblue.com> Authored: Mon Aug 25 00:13:32 2014 +0200 Committer: Rohit Yadav <rohit.ya...@shapeblue.com> Committed: Mon Aug 25 17:33:28 2014 +0200 ---------------------------------------------------------------------- .../command/GetServiceProviderMetaDataCmd.java | 202 +++++++++++++++++++ .../api/response/SAMLMetaDataResponse.java | 40 ++++ .../cloudstack/saml/SAML2AuthManagerImpl.java | 2 + 3 files changed, 244 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/99ae373b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java new file mode 100644 index 0000000..16ee088 --- /dev/null +++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java @@ -0,0 +1,202 @@ +// 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 com.cloud.api.response.ApiResponseSerializer; +import com.cloud.user.Account; +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.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.EntityDescriptor; +import org.opensaml.saml2.metadata.KeyDescriptor; +import org.opensaml.saml2.metadata.NameIDFormat; +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.EntityDescriptorBuilder; +import org.opensaml.saml2.metadata.impl.KeyDescriptorBuilder; +import org.opensaml.saml2.metadata.impl.NameIDFormatBuilder; +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 javax.inject.Inject; +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.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; + +@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.ACCOUNT_TYPE_NORMAL; + } + + @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, String remoteAddress, String responseType, StringBuilder auditTrailSb, 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)); + } + + EntityDescriptor spEntityDescriptor = new EntityDescriptorBuilder().buildObject(); + spEntityDescriptor.setEntityID(_samlAuthManager.getServiceProviderId()); + + SPSSODescriptor spSSODescriptor = new SPSSODescriptorBuilder().buildObject(); + spSSODescriptor.setWantAssertionsSigned(true); + spSSODescriptor.setAuthnRequestsSigned(false); + + X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory(); + keyInfoGeneratorFactory.setEmitEntityCertificate(true); + KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance(); + + KeyDescriptor encKeyDescriptor = new KeyDescriptorBuilder().buildObject(); + encKeyDescriptor.setUse(UsageType.ENCRYPTION); + + KeyDescriptor signKeyDescriptor = new KeyDescriptorBuilder().buildObject(); + signKeyDescriptor.setUse(UsageType.SIGNING); + + BasicX509Credential credential = new BasicX509Credential(); + credential.setEntityCertificate(_samlAuthManager.getIdpSigningKey()); + try { + encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential)); + signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential)); + //TODO: generate own pub/priv keys + //spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor); + //spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor); + } catch (SecurityException ignored) { + } + + NameIDFormat nameIDFormat = new NameIDFormatBuilder().buildObject(); + nameIDFormat.setFormat(NameIDType.PERSISTENT); + spSSODescriptor.getNameIDFormats().add(nameIDFormat); + + AssertionConsumerService assertionConsumerService = new AssertionConsumerServiceBuilder().buildObject(); + assertionConsumerService.setIndex(0); + assertionConsumerService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + assertionConsumerService.setLocation(_samlAuthManager.getSpSingleSignOnUrl()); + + SingleLogoutService ssoService = new SingleLogoutServiceBuilder().buildObject(); + ssoService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + ssoService.setLocation(_samlAuthManager.getSpSingleLogOutUrl()); + + spSSODescriptor.getSingleLogoutServices().add(ssoService); + spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService); + spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); + spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor); + + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + Marshaller out = Configuration.getMarshallerFactory().getMarshaller(spEntityDescriptor); + out.marshall(spEntityDescriptor, document); + + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + StringWriter stringWriter = new StringWriter(); + 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) { + response.setMetadata("Error creating Service Provider MetaData XML: " + e.getMessage()); + } + + return ApiResponseSerializer.toSerializedString(response, responseType); + } + + @Override + public APIAuthenticationType getAPIType() { + return APIAuthenticationType.LOGIN_API; + } + + @Override + public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) { + for (PluggableAPIAuthenticator authManager: authenticators) { + if (authManager instanceof SAML2AuthManager) { + _samlAuthManager = (SAML2AuthManager) authManager; + } + } + if (_samlAuthManager == null) { + s_logger.error("No suitable Pluggable Authentication Manager found for SAML2 Login Cmd"); + } + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/99ae373b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SAMLMetaDataResponse.java ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SAMLMetaDataResponse.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SAMLMetaDataResponse.java new file mode 100644 index 0000000..e091ea4 --- /dev/null +++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SAMLMetaDataResponse.java @@ -0,0 +1,40 @@ +// 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.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.BaseResponse; + +public class SAMLMetaDataResponse extends BaseResponse { + + @SerializedName("metadata") + @Param(description = "The Metadata XML") + private String metadata; + + public SAMLMetaDataResponse() { + super(); + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/99ae373b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java index 7ef126a..22d99cb 100644 --- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java +++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.saml; import com.cloud.configuration.Config; import com.cloud.utils.component.AdapterBase; import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator; +import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd; import org.apache.cloudstack.api.command.SAML2LoginAPIAuthenticatorCmd; import org.apache.cloudstack.api.command.SAML2LogoutAPIAuthenticatorCmd; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -147,6 +148,7 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage List<Class<?>> cmdList = new ArrayList<Class<?>>(); cmdList.add(SAML2LoginAPIAuthenticatorCmd.class); cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class); + cmdList.add(GetServiceProviderMetaDataCmd.class); return cmdList; }