You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2015/06/03 20:30:57 UTC

[2/2] git commit: updated refs/heads/saml-production-grade to b8b21aa

CLOUDSTACK-8458: Support SAML Federation, where there may be more than one IdP

- New datastructure to hold metadata of SP or IdP
- Recursive processing of IdP metadata
- Fix login/logout APIs to get new interface and metadata data structure
- Add org/contact information to metadata
- Add new API: listIdps that returns list of all discovered IdPs
- Refactor and cleanup code and tests

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/b8b21aa9
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/b8b21aa9
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/b8b21aa9

Branch: refs/heads/saml-production-grade
Commit: b8b21aa99dae19522ce5fb2d52c4c8f1bfb2744d
Parents: 79ef7f3
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Wed Jun 3 20:26:07 2015 +0200
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Wed Jun 3 20:29:43 2015 +0200

----------------------------------------------------------------------
 .../org/apache/cloudstack/api/ApiConstants.java |   2 +-
 .../command/GetServiceProviderMetaDataCmd.java  |  65 ++++-
 .../cloudstack/api/command/ListIdpsCmd.java     | 107 +++++++
 .../command/SAML2LoginAPIAuthenticatorCmd.java  |  79 +++--
 .../command/SAML2LogoutAPIAuthenticatorCmd.java |  19 +-
 .../cloudstack/api/response/IdpResponse.java    |  62 ++++
 .../cloudstack/saml/SAML2AuthManager.java       |  51 ++--
 .../cloudstack/saml/SAML2AuthManagerImpl.java   | 287 ++++++++++++-------
 .../cloudstack/saml/SAML2UserAuthenticator.java |   4 +-
 .../cloudstack/saml/SAMLPluginConstants.java    |  27 ++
 .../cloudstack/saml/SAMLProviderMetadata.java   | 122 ++++++++
 .../org/apache/cloudstack/saml/SAMLUtils.java   |  39 ++-
 .../GetServiceProviderMetaDataCmdTest.java      |  11 -
 .../cloudstack/SAML2UserAuthenticatorTest.java  |   4 +-
 .../org/apache/cloudstack/SAMLUtilsTest.java    |   8 +-
 .../SAML2LoginAPIAuthenticatorCmdTest.java      |   7 +-
 .../SAML2LogoutAPIAuthenticatorCmdTest.java     |  13 -
 ui/css/cloudstack3.css                          |   2 +-
 18 files changed, 657 insertions(+), 252 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/api/src/org/apache/cloudstack/api/ApiConstants.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java
index b6aed6f..1b5e0cb 100755
--- a/api/src/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/org/apache/cloudstack/api/ApiConstants.java
@@ -515,7 +515,7 @@ public class ApiConstants {
     public static final String VMPROFILE_ID = "vmprofileid";
     public static final String VMGROUP_ID = "vmgroupid";
     public static final String CS_URL = "csurl";
-    public static final String IDP_URL = "idpurl";
+    public static final String IDP_ID = "idpid";
     public static final String SCALEUP_POLICY_IDS = "scaleuppolicyids";
     public static final String SCALEDOWN_POLICY_IDS = "scaledownpolicyids";
     public static final String SCALEUP_POLICIES = "scaleuppolicies";

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/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
index 0997f48..0af02d5 100644
--- 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
@@ -30,21 +30,36 @@ 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.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;
@@ -73,6 +88,7 @@ import javax.xml.transform.stream.StreamResult;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 @APICommand(name = "getSPMetadata", description = "Returns SAML2 CloudStack Service Provider MetaData", responseObject = SAMLMetaDataResponse.class, entityType = {})
@@ -118,8 +134,10 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent
                     params, responseType));
         }
 
+        final SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata();
+
         EntityDescriptor spEntityDescriptor = new EntityDescriptorBuilder().buildObject();
-        spEntityDescriptor.setEntityID(_samlAuthManager.getServiceProviderId());
+        spEntityDescriptor.setEntityID(spMetadata.getEntityId());
 
         SPSSODescriptor spSSODescriptor = new SPSSODescriptorBuilder().buildObject();
         spSSODescriptor.setWantAssertionsSigned(true);
@@ -129,19 +147,23 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent
         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);
 
-        KeyDescriptor signKeyDescriptor = new KeyDescriptorBuilder().buildObject();
-        signKeyDescriptor.setUse(UsageType.SIGNING);
+        BasicX509Credential signingCredential = new BasicX509Credential();
+        signingCredential.setEntityCertificate(spMetadata.getSigningCertificate());
+
+        BasicX509Credential encryptionCredential = new BasicX509Credential();
+        encryptionCredential.setEntityCertificate(spMetadata.getEncryptionCertificate());
 
-        BasicX509Credential credential = new BasicX509Credential();
-        credential.setEntityCertificate(_samlAuthManager.getSpX509Certificate());
         try {
-            encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential));
-            signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential));
-            spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor);
+            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());
         }
@@ -162,28 +184,47 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent
         assertionConsumerService.setIndex(1);
         assertionConsumerService.setIsDefault(true);
         assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
-        assertionConsumerService.setLocation(_samlAuthManager.getSpSingleSignOnUrl());
+        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(_samlAuthManager.getSpSingleSignOnUrl());
+        assertionConsumerService2.setLocation(spMetadata.getSsoUrl());
         spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService2);
 
         SingleLogoutService ssoService = new SingleLogoutServiceBuilder().buildObject();
         ssoService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
-        ssoService.setLocation(_samlAuthManager.getSpSingleLogOutUrl());
+        ssoService.setLocation(spMetadata.getSloUrl());
         spSSODescriptor.getSingleLogoutServices().add(ssoService);
 
         SingleLogoutService ssoService2 = new SingleLogoutServiceBuilder().buildObject();
         ssoService2.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
-        ssoService2.setLocation(_samlAuthManager.getSpSingleLogOutUrl());
+        ssoService2.setLocation(spMetadata.getSloUrl());
         spSSODescriptor.getSingleLogoutServices().add(ssoService2);
 
         spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS);
         spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor);
 
+        ContactPerson contactPerson = new ContactPersonBuilder().buildObject();
+        GivenName givenName = new GivenNameBuilder().buildObject();
+        givenName.setName(spMetadata.getContactPersonName());
+        EmailAddress emailAddress = new EmailAddressBuilder().buildObject();
+        emailAddress.setAddress(spMetadata.getContactPersonEmail());
+        contactPerson.setType(ContactPersonTypeEnumeration.TECHNICAL);
+        contactPerson.setGivenName(givenName);
+        contactPerson.getEmailAddresses().add(emailAddress);
+        spEntityDescriptor.getContactPersons().add(contactPerson);
+
+        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 = DocumentBuilderFactory.newInstance();

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListIdpsCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListIdpsCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListIdpsCmd.java
new file mode 100644
index 0000000..4786a62
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListIdpsCmd.java
@@ -0,0 +1,107 @@
+// 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.IdpResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLProviderMetadata;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@APICommand(name = "listIdps", description = "Returns list of discovered SAML 2.0 Identity Providers", responseObject = IdpResponse.class, entityType = {})
+public class ListIdpsCmd extends BaseCmd implements APIAuthenticator {
+    public static final Logger s_logger = Logger.getLogger(ListIdpsCmd.class.getName());
+    private static final String s_name = "listidpsresponse";
+
+    @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 {
+        // We should never reach here
+        throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
+    }
+
+    @Override
+    public String authenticate(String command, Map<String, Object[]> params, HttpSession session, String remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
+        auditTrailSb.append("=== SAML List IdPs ===");
+        ListResponse<IdpResponse> response = new ListResponse<IdpResponse>();
+        List<IdpResponse> idpResponseList = new ArrayList<IdpResponse>();
+        for (SAMLProviderMetadata metadata: _samlAuthManager.getAllIdPMetadata()) {
+            IdpResponse idpResponse = new IdpResponse();
+            idpResponse.setId(metadata.getEntityId());
+            idpResponse.setOrgName(metadata.getOrganizationName());
+            idpResponse.setOrgUrl(metadata.getOrganizationUrl());
+            idpResponse.setObjectName("idp");
+            idpResponseList.add(idpResponse);
+        }
+        response.setResponses(idpResponseList, idpResponseList.size());
+        response.setResponseName(getCommandName());
+        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 != null && authManager instanceof SAML2AuthManager) {
+                _samlAuthManager = (SAML2AuthManager) authManager;
+            }
+        }
+        if (_samlAuthManager == null) {
+            s_logger.error("No suitable Pluggable Authentication Manager found for SAML2 Login Cmd");
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
index 3fee5b2..ee7bfc9 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
@@ -39,12 +39,14 @@ import org.apache.cloudstack.api.auth.APIAuthenticator;
 import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
 import org.apache.cloudstack.api.response.LoginCmdResponse;
 import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLPluginConstants;
+import org.apache.cloudstack.saml.SAMLProviderMetadata;
 import org.apache.cloudstack.saml.SAMLUtils;
 import org.apache.log4j.Logger;
 import org.opensaml.DefaultBootstrap;
 import org.opensaml.saml2.core.Assertion;
-import org.opensaml.saml2.core.AuthnRequest;
 import org.opensaml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml2.core.Issuer;
 import org.opensaml.saml2.core.Response;
 import org.opensaml.saml2.core.StatusCode;
 import org.opensaml.saml2.encryption.Decrypter;
@@ -52,7 +54,6 @@ import org.opensaml.xml.ConfigurationException;
 import org.opensaml.xml.encryption.DecryptionException;
 import org.opensaml.xml.encryption.EncryptedKeyResolver;
 import org.opensaml.xml.encryption.InlineEncryptedKeyResolver;
-import org.opensaml.xml.io.MarshallingException;
 import org.opensaml.xml.io.UnmarshallingException;
 import org.opensaml.xml.security.SecurityHelper;
 import org.opensaml.xml.security.credential.Credential;
@@ -72,9 +73,6 @@ import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.stream.FactoryConfigurationError;
 import java.io.IOException;
 import java.net.URLEncoder;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
 import java.util.List;
 import java.util.Map;
 
@@ -86,8 +84,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
     /////////////////////////////////////////////////////
     //////////////// API parameters /////////////////////
     /////////////////////////////////////////////////////
-    @Parameter(name = ApiConstants.IDP_URL, type = CommandType.STRING, description = "Identity Provider SSO HTTP-Redirect binding URL", required = true)
-    private String idpUrl;
+    @Parameter(name = ApiConstants.IDP_ID, type = CommandType.STRING, description = "Identity Provider Entity ID", required = true)
+    private String idpId;
 
     @Inject
     ApiServerService _apiServer;
@@ -104,8 +102,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
 
-    public String getIdpUrl() {
-        return idpUrl;
+    public String getIdpId() {
+        return idpId;
     }
 
     /////////////////////////////////////////////////////
@@ -128,30 +126,6 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
         throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
     }
 
-    private String buildAuthnRequestUrl(String idpUrl) {
-        String spId = _samlAuthManager.getServiceProviderId();
-        String consumerUrl = _samlAuthManager.getSpSingleSignOnUrl();
-        String identityProviderUrl = _samlAuthManager.getIdpSingleSignOnUrl();
-
-        if (idpUrl != null) {
-            identityProviderUrl = idpUrl;
-        }
-
-        String redirectUrl = "";
-        try {
-            DefaultBootstrap.bootstrap();
-            AuthnRequest authnRequest = SAMLUtils.buildAuthnRequestObject(spId, identityProviderUrl, consumerUrl);
-            PrivateKey privateKey = null;
-            if (_samlAuthManager.getSpKeyPair() != null) {
-                privateKey = _samlAuthManager.getSpKeyPair().getPrivate();
-            }
-            redirectUrl = identityProviderUrl + "?" + SAMLUtils.generateSAMLRequestSignature("SAMLRequest=" + SAMLUtils.encodeSAMLRequest(authnRequest), privateKey);
-        } catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException | NoSuchAlgorithmException | InvalidKeyException | java.security.SignatureException e) {
-            s_logger.error("SAML AuthnRequest message building error: " + e.getMessage());
-        }
-        return redirectUrl;
-    }
-
     public Response processSAMLResponse(String responseMessage) {
         Response responseObject = null;
         try {
@@ -168,12 +142,19 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
     public String authenticate(final String command, final Map<String, Object[]> params, final HttpSession session, final String remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
         try {
             if (!params.containsKey("SAMLResponse") && !params.containsKey("SAMLart")) {
-                String idpUrl = null;
-                final String[] idps = (String[])params.get(ApiConstants.IDP_URL);
+                String idpId = null;
+                final String[] idps = (String[])params.get(ApiConstants.IDP_ID);
                 if (idps != null && idps.length > 0) {
-                    idpUrl = idps[0];
+                    idpId = idps[0];
+                }
+                SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata();
+                SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(idpId);
+                if (idpMetadata == null) {
+                    throw new ServerApiException(ApiErrorCode.PARAM_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
+                            "IdP ID (" + idpId + ") is not found in our list of supported IdPs, cannot proceed.",
+                            params, responseType));
                 }
-                String redirectUrl = this.buildAuthnRequestUrl(idpUrl);
+                String redirectUrl = SAMLUtils.buildAuthnRequestUrl(spMetadata, idpMetadata);
                 resp.sendRedirect(redirectUrl);
                 return "";
             } if (params.containsKey("SAMLart")) {
@@ -181,7 +162,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
                         "SAML2 HTTP Artifact Binding is not supported",
                         params, responseType));
             } else {
-                final String samlResponse = ((String[])params.get(SAMLUtils.SAML_RESPONSE))[0];
+                final String samlResponse = ((String[])params.get(SAMLPluginConstants.SAML_RESPONSE))[0];
                 Response processedSAMLResponse = this.processSAMLResponse(samlResponse);
                 String statusCode = processedSAMLResponse.getStatus().getStatusCode().getValue();
                 if (!statusCode.equals(StatusCode.SUCCESS_URI)) {
@@ -206,10 +187,17 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
                     s_logger.error("The default domain ID for SAML users is not set correct, it should be a UUID. ROOT domain will be used.");
                 }
 
+                Issuer issuer = processedSAMLResponse.getIssuer();
+                SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata();
+                SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(issuer.getValue());
+
+                // Set IdpId for this session
+                session.setAttribute(SAMLPluginConstants.SAML_IDPID, issuer.getValue());
+
                 Signature sig = processedSAMLResponse.getSignature();
-                if (_samlAuthManager.getIdpSigningKey() != null && sig != null) {
+                if (idpMetadata.getSigningCertificate() != null && sig != null) {
                     BasicX509Credential credential = new BasicX509Credential();
-                    credential.setEntityCertificate(_samlAuthManager.getIdpSigningKey());
+                    credential.setEntityCertificate(idpMetadata.getSigningCertificate());
                     SignatureValidator validator = new SignatureValidator(credential);
                     try {
                         validator.validate(sig);
@@ -223,9 +211,10 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
                 if (username == null) {
                     username = SAMLUtils.getValueFromAssertions(processedSAMLResponse.getAssertions(), SAML2AuthManager.SAMLUserAttributeName.value());
                 }
-                if (_samlAuthManager.getIdpEncryptionKey() != null && _samlAuthManager.getSpKeyPair() != null &&  _samlAuthManager.getSpKeyPair().getPrivate() != null) {
-                    Credential credential = SecurityHelper.getSimpleCredential(_samlAuthManager.getIdpEncryptionKey().getPublicKey(),
-                            _samlAuthManager.getSpKeyPair().getPrivate());
+                if (idpMetadata.getEncryptionCertificate() != null && spMetadata != null
+                        && spMetadata.getKeyPair() != null && spMetadata.getKeyPair().getPrivate() != null) {
+                    Credential credential = SecurityHelper.getSimpleCredential(idpMetadata.getEncryptionCertificate().getPublicKey(),
+                            spMetadata.getKeyPair().getPrivate());
                     StaticKeyInfoCredentialResolver keyInfoResolver = new StaticKeyInfoCredentialResolver(credential);
                     EncryptedKeyResolver keyResolver = new InlineEncryptedKeyResolver();
                     Decrypter decrypter = new Decrypter(null, keyInfoResolver, keyResolver);
@@ -243,9 +232,9 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
                                 continue;
                             }
                             Signature encSig = assertion.getSignature();
-                            if (_samlAuthManager.getIdpSigningKey() != null && encSig != null) {
+                            if (idpMetadata.getSigningCertificate() != null && encSig != null) {
                                 BasicX509Credential sigCredential = new BasicX509Credential();
-                                sigCredential.setEntityCertificate(_samlAuthManager.getIdpSigningKey());
+                                sigCredential.setEntityCertificate(idpMetadata.getSigningCertificate());
                                 SignatureValidator validator = new SignatureValidator(sigCredential);
                                 try {
                                     validator.validate(encSig);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
index 18ff630..844176d 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
@@ -27,13 +27,13 @@ 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.LogoutCmdResponse;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLPluginConstants;
+import org.apache.cloudstack.saml.SAMLProviderMetadata;
 import org.apache.cloudstack.saml.SAMLUtils;
 import org.apache.log4j.Logger;
 import org.opensaml.DefaultBootstrap;
 import org.opensaml.saml2.core.LogoutRequest;
-import org.opensaml.saml2.core.NameID;
 import org.opensaml.saml2.core.Response;
 import org.opensaml.saml2.core.StatusCode;
 import org.opensaml.xml.ConfigurationException;
@@ -58,8 +58,7 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
 
     @Inject
     ApiServerService _apiServer;
-    @Inject
-    ConfigurationDao _configDao;
+
     SAML2AuthManager _samlAuthManager;
 
     /////////////////////////////////////////////////////
@@ -109,7 +108,7 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
 
         if (params != null && params.containsKey("SAMLResponse")) {
             try {
-                final String samlResponse = ((String[])params.get(SAMLUtils.SAML_RESPONSE))[0];
+                final String samlResponse = ((String[])params.get(SAMLPluginConstants.SAML_RESPONSE))[0];
                 Response processedSAMLResponse = SAMLUtils.decodeSAMLResponse(samlResponse);
                 String statusCode = processedSAMLResponse.getStatus().getStatusCode().getValue();
                 if (!statusCode.equals(StatusCode.SUCCESS_URI)) {
@@ -127,19 +126,19 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
             return responseString;
         }
 
-        NameID nameId = (NameID) session.getAttribute(SAMLUtils.SAML_NAMEID);
-        String sessionIndex = (String) session.getAttribute(SAMLUtils.SAML_SESSION);
-        if (nameId == null || sessionIndex == null) {
+        String idpId = (String) session.getAttribute(SAMLPluginConstants.SAML_IDPID);
+        SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(idpId);
+        if (idpMetadata == null) {
             try {
                 resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value());
             } catch (IOException ignored) {
             }
             return responseString;
         }
-        LogoutRequest logoutRequest = SAMLUtils.buildLogoutRequest(_samlAuthManager.getIdpSingleLogOutUrl(), _samlAuthManager.getServiceProviderId(), nameId, sessionIndex);
+        LogoutRequest logoutRequest = SAMLUtils.buildLogoutRequest(idpMetadata.getSloUrl(), _samlAuthManager.getSPMetadata().getEntityId());
 
         try {
-            String redirectUrl = _samlAuthManager.getIdpSingleLogOutUrl() + "?SAMLRequest=" + SAMLUtils.encodeSAMLRequest(logoutRequest);
+            String redirectUrl = idpMetadata.getSloUrl() + "?SAMLRequest=" + SAMLUtils.encodeSAMLRequest(logoutRequest);
             resp.sendRedirect(redirectUrl);
         } catch (MarshallingException | IOException e) {
             s_logger.error("SAML SLO error: " + e.getMessage());

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/IdpResponse.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/IdpResponse.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/IdpResponse.java
new file mode 100644
index 0000000..d95cc33
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/IdpResponse.java
@@ -0,0 +1,62 @@
+// 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;
+
+public class IdpResponse extends AuthenticationCmdResponse {
+    @SerializedName("id")
+    @Param(description = "The IdP Entity ID")
+    private String id;
+
+    @SerializedName("orgName")
+    @Param(description = "The IdP Organization Name")
+    private String orgName;
+
+    @SerializedName("orgUrl")
+    @Param(description = "The IdP Organization URL")
+    private String orgUrl;
+
+    public IdpResponse() {
+        super();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getOrgName() {
+        return orgName;
+    }
+
+    public void setOrgName(String orgName) {
+        this.orgName = orgName;
+    }
+
+    public String getOrgUrl() {
+        return orgUrl;
+    }
+
+    public void setOrgUrl(String orgUrl) {
+        this.orgUrl = orgUrl;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
index 468e9df..6c856d5 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
@@ -20,22 +20,27 @@ package org.apache.cloudstack.saml;
 import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
 import org.apache.cloudstack.framework.config.ConfigKey;
 
-import java.security.KeyPair;
-import java.security.cert.X509Certificate;
+import java.util.Collection;
 
 public interface SAML2AuthManager extends PluggableAPIAuthenticator {
 
     public static final ConfigKey<Boolean> SAMLIsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.enabled", "false",
             "Indicates whether SAML SSO plugin is enabled or not", true);
 
-    public static final ConfigKey<String> SAMLUserAttributeName = new ConfigKey<String>("Advanced", String.class, "saml2.user.attribute", "uid",
-            "Attribute name to be looked for in SAML response that will contain the username", true);
+    public static final ConfigKey<String> SAMLServiceProviderID = new ConfigKey<String>("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack",
+            "SAML2 Service Provider Identifier String", true);
 
-    public static final ConfigKey<String> SAMLDefaultDomain = new ConfigKey<String>("Advanced", String.class, "saml2.default.domainid", "1",
-            "The default domain UUID to use if domain information is not found while authenticating users", true);
+    public static final ConfigKey<String> SAMLServiceProviderContactPersonName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.person", "CloudStack Developers",
+            "SAML2 Service Provider Contact Person Name", true);
 
-    public static final ConfigKey<String> SAMLCloudStackRedirectionUrl = new ConfigKey<String>("Advanced", String.class, "saml2.redirect.url", "http://localhost:8080/client",
-            "The CloudStack UI url the SSO should redirected to when successful", true);
+    public static final ConfigKey<String> SAMLServiceProviderContactEmail = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.email", "dev@cloudstack.apache.org",
+            "SAML2 Service Provider Contact Email Address", true);
+
+    public static final ConfigKey<String> SAMLServiceProviderOrgName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.name", "Apache CloudStack",
+            "SAML2 Service Provider Organization Name", true);
+
+    public static final ConfigKey<String> SAMLServiceProviderOrgUrl = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.url", "http://cloudstack.apache.org",
+            "SAML2 Service Provider Organization URL", true);
 
     public static final ConfigKey<String> SAMLServiceProviderSingleSignOnURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.sso.url", "http://localhost:8080/client/api?command=samlSso",
             "SAML2 CloudStack Service Provider Single Sign On URL", true);
@@ -43,29 +48,25 @@ public interface SAML2AuthManager extends PluggableAPIAuthenticator {
     public static final ConfigKey<String> SAMLServiceProviderSingleLogOutURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.slo.url", "http://localhost:8080/client/api?command=samlSlo",
             "SAML2 CloudStack Service Provider Single Log Out URL", true);
 
-    public static final ConfigKey<String> SAMLServiceProviderID = new ConfigKey<String>("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack",
-            "SAML2 Service Provider Identifier String", true);
+    public static final ConfigKey<String> SAMLCloudStackRedirectionUrl = new ConfigKey<String>("Advanced", String.class, "saml2.redirect.url", "http://localhost:8080/client",
+            "The CloudStack UI url the SSO should redirected to when successful", true);
+
+    public static final ConfigKey<String> SAMLUserAttributeName = new ConfigKey<String>("Advanced", String.class, "saml2.user.attribute", "uid",
+            "Attribute name to be looked for in SAML response that will contain the username", true);
+
+    public static final ConfigKey<String> SAMLDefaultDomain = new ConfigKey<String>("Advanced", String.class, "saml2.default.domainid", "1",
+            "The default domain UUID to use if domain information is not found while authenticating users", true);
 
     public static final ConfigKey<String> SAMLIdentityProviderMetadataURL = new ConfigKey<String>("Advanced", String.class, "saml2.idp.metadata.url", "https://openidp.feide.no/simplesaml/saml2/idp/metadata.php",
             "SAML2 Identity Provider Metadata XML Url", true);
 
-    public static final ConfigKey<String> SAMLIdentityProviderId = new ConfigKey<String>("Advanced", String.class, "saml2.idp.id", "https://openidp.feide.no",
-            "SAML2 Identity Provider Metadata XML Url", true);
+    public static final ConfigKey<String> SAMLDefaultIdentityProviderId = new ConfigKey<String>("Advanced", String.class, "saml2.default.idpid", "https://openidp.feide.no",
+            "The default IdP entity ID to use only in case of multiple IdPs", true);
 
     public static final ConfigKey<Integer> SAMLTimeout = new ConfigKey<Integer>("Advanced", Integer.class, "saml2.timeout", "30000",
             "SAML2 IDP Metadata Downloading and parsing etc. activity timeout in milliseconds", true);
 
-    public String getServiceProviderId();
-    public String getIdentityProviderId();
-
-    public X509Certificate getIdpSigningKey();
-    public X509Certificate getIdpEncryptionKey();
-    public X509Certificate getSpX509Certificate();
-    public KeyPair getSpKeyPair();
-
-    public String getSpSingleSignOnUrl();
-    public String getIdpSingleSignOnUrl();
-
-    public String getSpSingleLogOutUrl();
-    public String getIdpSingleLogOutUrl();
+    public SAMLProviderMetadata getSPMetadata();
+    public SAMLProviderMetadata getIdPMetadata(String entityId);
+    public Collection<SAMLProviderMetadata> getAllIdPMetadata();
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/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 6849eb8..6797bed 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,25 +19,32 @@ package org.apache.cloudstack.saml;
 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.ListIdpsCmd;
 import org.apache.cloudstack.api.command.SAML2LoginAPIAuthenticatorCmd;
 import org.apache.cloudstack.api.command.SAML2LogoutAPIAuthenticatorCmd;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
 import org.apache.cloudstack.framework.security.keystore.KeystoreVO;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.log4j.Logger;
 import org.opensaml.DefaultBootstrap;
 import org.opensaml.common.xml.SAMLConstants;
+import org.opensaml.saml2.metadata.ContactPerson;
+import org.opensaml.saml2.metadata.EmailAddress;
+import org.opensaml.saml2.metadata.EntitiesDescriptor;
 import org.opensaml.saml2.metadata.EntityDescriptor;
 import org.opensaml.saml2.metadata.IDPSSODescriptor;
 import org.opensaml.saml2.metadata.KeyDescriptor;
+import org.opensaml.saml2.metadata.OrganizationDisplayName;
+import org.opensaml.saml2.metadata.OrganizationName;
+import org.opensaml.saml2.metadata.OrganizationURL;
 import org.opensaml.saml2.metadata.SingleLogoutService;
 import org.opensaml.saml2.metadata.SingleSignOnService;
 import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
 import org.opensaml.xml.ConfigurationException;
+import org.opensaml.xml.XMLObject;
 import org.opensaml.xml.parse.BasicParserPool;
 import org.opensaml.xml.security.credential.UsageType;
 import org.opensaml.xml.security.keyinfo.KeyInfoHelper;
@@ -63,33 +70,25 @@ import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 @Component
 @Local(value = {SAML2AuthManager.class, PluggableAPIAuthenticator.class})
 public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManager, Configurable {
     private static final Logger s_logger = Logger.getLogger(SAML2AuthManagerImpl.class);
 
-    private String serviceProviderId;
-    private String identityProviderId;
+    private SAMLProviderMetadata _spMetadata = new SAMLProviderMetadata();
+    private Map<String, SAMLProviderMetadata> _idpMetadataMap = new HashMap<String, SAMLProviderMetadata>();
 
-    private X509Certificate idpSigningKey;
-    private X509Certificate idpEncryptionKey;
-    private X509Certificate spX509Key;
-    private KeyPair spKeyPair;
-
-    private String spSingleSignOnUrl;
     private String idpSingleSignOnUrl;
-
-    private String spSingleLogOutUrl;
     private String idpSingleLogOutUrl;
 
     private HTTPMetadataProvider idpMetaDataProvider;
 
     @Inject
-    ConfigurationDao _configDao;
-
-    @Inject
     private KeystoreDao _ksDao;
 
     @Override
@@ -103,24 +102,34 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
         return super.start();
     }
 
-    private boolean setup() {
-        KeystoreVO keyStoreVO = _ksDao.findByName(SAMLUtils.SAMLSP_KEYPAIR);
+    private boolean initSP() {
+        KeystoreVO keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
         if (keyStoreVO == null) {
             try {
                 KeyPair keyPair = SAMLUtils.generateRandomKeyPair();
-                _ksDao.save(SAMLUtils.SAMLSP_KEYPAIR, SAMLUtils.savePrivateKey(keyPair.getPrivate()), SAMLUtils.savePublicKey(keyPair.getPublic()), "samlsp-keypair");
-                keyStoreVO = _ksDao.findByName(SAMLUtils.SAMLSP_KEYPAIR);
+                _ksDao.save(SAMLPluginConstants.SAMLSP_KEYPAIR, SAMLUtils.savePrivateKey(keyPair.getPrivate()), SAMLUtils.savePublicKey(keyPair.getPublic()), "samlsp-keypair");
+                keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
+                s_logger.info("No SAML keystore found, created and saved a new Service Provider keypair");
             } catch (NoSuchProviderException | NoSuchAlgorithmException e) {
-                s_logger.error("Unable to create and save SAML keypair");
+                s_logger.error("Unable to create and save SAML keypair: " + e.toString());
             }
         }
 
+        String spId = SAMLServiceProviderID.value();
+        String spSsoUrl = SAMLServiceProviderSingleSignOnURL.value();
+        String spSloUrl = SAMLServiceProviderSingleLogOutURL.value();
+        String spOrgName = SAMLServiceProviderOrgName.value();
+        String spOrgUrl = SAMLServiceProviderOrgUrl.value();
+        String spContactPersonName = SAMLServiceProviderContactPersonName.value();
+        String spContactPersonEmail = SAMLServiceProviderContactEmail.value();
+        KeyPair spKeyPair = null;
+        X509Certificate spX509Key = null;
         if (keyStoreVO != null) {
             PrivateKey privateKey = SAMLUtils.loadPrivateKey(keyStoreVO.getCertificate());
             PublicKey publicKey = SAMLUtils.loadPublicKey(keyStoreVO.getKey());
             if (privateKey != null && publicKey != null) {
                 spKeyPair = new KeyPair(publicKey, privateKey);
-                KeystoreVO x509VO = _ksDao.findByName(SAMLUtils.SAMLSP_X509CERT);
+                KeystoreVO x509VO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_X509CERT);
                 if (x509VO == null) {
                     try {
                         spX509Key = SAMLUtils.generateRandomX509Certificate(spKeyPair);
@@ -128,7 +137,7 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
                         ObjectOutput out = new ObjectOutputStream(bos);
                         out.writeObject(spX509Key);
                         out.flush();
-                        _ksDao.save(SAMLUtils.SAMLSP_X509CERT, Base64.encodeBase64String(bos.toByteArray()), "", "samlsp-x509cert");
+                        _ksDao.save(SAMLPluginConstants.SAMLSP_X509CERT, Base64.encodeBase64String(bos.toByteArray()), "", "samlsp-x509cert");
                         bos.close();
                     } catch (NoSuchAlgorithmException | NoSuchProviderException | CertificateEncodingException | SignatureException | InvalidKeyException | IOException e) {
                         s_logger.error("SAML Plugin won't be able to use X509 signed authentication");
@@ -145,52 +154,107 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
                 }
             }
         }
+        if (spKeyPair != null && spX509Key != null
+                && spId != null && spSsoUrl != null && spSloUrl != null
+                && spOrgName != null && spOrgUrl != null
+                && spContactPersonName != null && spContactPersonEmail != null) {
+            _spMetadata.setEntityId(spId);
+            _spMetadata.setOrganizationName(spOrgName);
+            _spMetadata.setOrganizationUrl(spOrgUrl);
+            _spMetadata.setContactPersonName(spContactPersonName);
+            _spMetadata.setContactPersonEmail(spContactPersonEmail);
+            _spMetadata.setSsoUrl(spSsoUrl);
+            _spMetadata.setSloUrl(spSloUrl);
+            _spMetadata.setKeyPair(spKeyPair);
+            _spMetadata.setSigningCertificate(spX509Key);
+            _spMetadata.setEncryptionCertificate(spX509Key);
+            return true;
+        }
+        return false;
+    }
 
-        this.serviceProviderId = SAMLServiceProviderID.value();
-        this.identityProviderId = SAMLIdentityProviderId.value();
-
-        this.spSingleSignOnUrl = SAMLServiceProviderSingleSignOnURL.value();
-        this.spSingleLogOutUrl = SAMLServiceProviderSingleLogOutURL.value();
-
-        String idpMetaDataUrl = SAMLIdentityProviderMetadataURL.value();
-
-        int tolerance = 30000;
-        tolerance = SAMLTimeout.value();
-
-        try {
-            DefaultBootstrap.bootstrap();
-            idpMetaDataProvider = new HTTPMetadataProvider(idpMetaDataUrl, tolerance);
-            idpMetaDataProvider.setRequireValidMetadata(true);
-            idpMetaDataProvider.setParserPool(new BasicParserPool());
-            idpMetaDataProvider.initialize();
+    private void addIdpToMap(EntityDescriptor descriptor, Map<String, SAMLProviderMetadata> idpMap) {
+        SAMLProviderMetadata idpMetadata = new SAMLProviderMetadata();
+        idpMetadata.setEntityId(descriptor.getEntityID());
+        s_logger.debug("Adding IdP to the list of discovered IdPs: " + descriptor.getEntityID());
+        if (descriptor.getOrganization() != null) {
+            if (descriptor.getOrganization().getDisplayNames() != null) {
+                for (OrganizationDisplayName orgName : descriptor.getOrganization().getDisplayNames()) {
+                    if (orgName != null && orgName.getName() != null) {
+                        idpMetadata.setOrganizationName(orgName.getName().getLocalString());
+                        break;
+                    }
+                }
+            }
+            if (idpMetadata.getOrganizationName() == null && descriptor.getOrganization().getOrganizationNames() != null) {
+                for (OrganizationName orgName : descriptor.getOrganization().getOrganizationNames()) {
+                    if (orgName != null && orgName.getName() != null) {
+                        idpMetadata.setOrganizationName(orgName.getName().getLocalString());
+                        break;
+                    }
+                }
+            }
+            if (descriptor.getOrganization().getURLs() != null) {
+                for (OrganizationURL organizationURL : descriptor.getOrganization().getURLs()) {
+                    if (organizationURL != null && organizationURL.getURL() != null) {
+                        idpMetadata.setOrganizationUrl(organizationURL.getURL().getLocalString());
+                        break;
+                    }
+                }
+            }
+        }
+        if (descriptor.getContactPersons() != null) {
+            for (ContactPerson person : descriptor.getContactPersons()) {
+                if (person == null || (person.getGivenName() == null && person.getSurName() == null)
+                        || person.getEmailAddresses() == null) {
+                    continue;
+                }
+                if (person.getGivenName() != null) {
+                    idpMetadata.setContactPersonName(person.getGivenName().getName());
 
-            EntityDescriptor idpEntityDescriptor = idpMetaDataProvider.getEntityDescriptor(this.identityProviderId);
+                } else if (person.getSurName() != null) {
+                    idpMetadata.setContactPersonName(person.getSurName().getName());
+                }
+                for (EmailAddress emailAddress : person.getEmailAddresses()) {
+                    if (emailAddress != null && emailAddress.getAddress() != null) {
+                        idpMetadata.setContactPersonEmail(emailAddress.getAddress());
+                    }
+                }
+                if (idpMetadata.getContactPersonName() != null && idpMetadata.getContactPersonEmail() != null) {
+                    break;
+                }
+            }
+        }
 
-            IDPSSODescriptor idpssoDescriptor = idpEntityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
-            if (idpssoDescriptor != null) {
-                for (SingleSignOnService ssos: idpssoDescriptor.getSingleSignOnServices()) {
+        IDPSSODescriptor idpDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
+        if (idpDescriptor != null) {
+            if (idpDescriptor.getSingleSignOnServices() != null) {
+                for (SingleSignOnService ssos : idpDescriptor.getSingleSignOnServices()) {
                     if (ssos.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
-                        this.idpSingleSignOnUrl = ssos.getLocation();
+                        idpMetadata.setSsoUrl(ssos.getLocation());
                     }
                 }
-
-                for (SingleLogoutService slos: idpssoDescriptor.getSingleLogoutServices()) {
+            }
+            if (idpDescriptor.getSingleLogoutServices() != null) {
+                for (SingleLogoutService slos : idpDescriptor.getSingleLogoutServices()) {
                     if (slos.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
-                        this.idpSingleLogOutUrl = slos.getLocation();
+                        idpMetadata.setSloUrl(slos.getLocation());
                     }
                 }
+            }
 
-                X509Certificate unspecifiedKey = null;
-                for (KeyDescriptor kd: idpssoDescriptor.getKeyDescriptors()) {
+            X509Certificate unspecifiedKey = null;
+            if (idpDescriptor.getKeyDescriptors() != null) {
+                for (KeyDescriptor kd : idpDescriptor.getKeyDescriptors()) {
                     if (kd.getUse() == UsageType.SIGNING) {
                         try {
-                            this.idpSigningKey = KeyInfoHelper.getCertificates(kd.getKeyInfo()).get(0);
+                            idpMetadata.setSigningCertificate(KeyInfoHelper.getCertificates(kd.getKeyInfo()).get(0));
                         } catch (CertificateException ignored) {
                         }
                     }
                     if (kd.getUse() == UsageType.ENCRYPTION) {
                         try {
-                            this.idpEncryptionKey = KeyInfoHelper.getCertificates(kd.getKeyInfo()).get(0);
+                            idpMetadata.setEncryptionCertificate(KeyInfoHelper.getCertificates(kd.getKeyInfo()).get(0));
                         } catch (CertificateException ignored) {
                         }
                     }
@@ -201,15 +265,55 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
                         }
                     }
                 }
-                if (idpSigningKey == null && unspecifiedKey != null) {
-                    idpSigningKey = unspecifiedKey;
+            }
+            if (idpMetadata.getSigningCertificate() == null && unspecifiedKey != null) {
+                idpMetadata.setSigningCertificate(unspecifiedKey);
+            }
+            if (idpMetadata.getEncryptionCertificate() == null && unspecifiedKey != null) {
+                idpMetadata.setEncryptionCertificate(unspecifiedKey);
+            }
+            if (idpMap.containsKey(idpMetadata.getEntityId())) {
+                s_logger.warn("Duplicate IdP metadata found with entity Id: " + idpMetadata.getEntityId());
+            }
+            idpMap.put(idpMetadata.getEntityId(), idpMetadata);
+        }
+    }
+
+    private void discoverAndAddIdp(XMLObject metadata, Map<String, SAMLProviderMetadata> idpMap) {
+        if (metadata instanceof EntityDescriptor) {
+            EntityDescriptor entityDescriptor = (EntityDescriptor) metadata;
+            addIdpToMap(entityDescriptor, idpMap);
+        } else if (metadata instanceof EntitiesDescriptor) {
+            EntitiesDescriptor entitiesDescriptor = (EntitiesDescriptor) metadata;
+            if (entitiesDescriptor.getEntityDescriptors() != null) {
+                for (EntityDescriptor entityDescriptor: entitiesDescriptor.getEntityDescriptors()) {
+                    addIdpToMap(entityDescriptor, idpMap);
                 }
-                if (idpEncryptionKey == null && unspecifiedKey != null) {
-                    idpEncryptionKey = unspecifiedKey;
+            }
+            if (entitiesDescriptor.getEntitiesDescriptors() != null) {
+                for (EntitiesDescriptor entitiesDescriptorInner: entitiesDescriptor.getEntitiesDescriptors()) {
+                    discoverAndAddIdp(entitiesDescriptorInner, idpMap);
                 }
-            } else {
-                s_logger.warn("Provided IDP XML Metadata does not contain IDPSSODescriptor, SAML authentication may not work");
             }
+        }
+    }
+
+    private boolean setup() {
+        if (!initSP()) {
+            s_logger.error("SAML Plugin failed to initialize, please fix the configuration and restart management server");
+            return false;
+        }
+        String idpMetaDataUrl = SAMLIdentityProviderMetadataURL.value();
+        int tolerance = 30000;
+        tolerance = SAMLTimeout.value();
+        try {
+            DefaultBootstrap.bootstrap();
+            idpMetaDataProvider = new HTTPMetadataProvider(idpMetaDataUrl, tolerance);
+            idpMetaDataProvider.setRequireValidMetadata(true);
+            idpMetaDataProvider.setParserPool(new BasicParserPool());
+            idpMetaDataProvider.initialize();
+            _idpMetadataMap.clear();
+            discoverAndAddIdp(idpMetaDataProvider.getMetadata(), _idpMetadataMap);
         } catch (MetadataProviderException e) {
             s_logger.error("Unable to read SAML2 IDP MetaData URL, error:" + e.getMessage());
             s_logger.error("SAML2 Authentication may be unavailable");
@@ -219,11 +323,6 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
             s_logger.error("Unable to setup SAML Auth Plugin due to NullPointerException" +
                     " please check the SAML IDP metadata URL and entity ID in global settings: " + e.getMessage());
         }
-
-        if (this.idpSingleLogOutUrl == null || this.idpSingleSignOnUrl == null) {
-            s_logger.error("SAML based authentication won't work");
-        }
-
         return true;
     }
 
@@ -236,54 +335,40 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
         cmdList.add(SAML2LoginAPIAuthenticatorCmd.class);
         cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class);
         cmdList.add(GetServiceProviderMetaDataCmd.class);
+        cmdList.add(ListIdpsCmd.class);
         return cmdList;
     }
 
-    public String getServiceProviderId() {
-        return serviceProviderId;
-    }
-
-    public String getIdpSingleSignOnUrl() {
-        return this.idpSingleSignOnUrl;
-    }
-
-    public String getIdpSingleLogOutUrl() {
-        return this.idpSingleLogOutUrl;
-    }
-
-    public String getSpSingleSignOnUrl() {
-        return spSingleSignOnUrl;
-    }
-
-    public String getSpSingleLogOutUrl() {
-        return spSingleLogOutUrl;
-    }
-
-    public String getIdentityProviderId() {
-        return identityProviderId;
+    @Override
+    public SAMLProviderMetadata getSPMetadata() {
+        return _spMetadata;
     }
 
-    public X509Certificate getIdpSigningKey() {
-        return idpSigningKey;
+    @Override
+    public SAMLProviderMetadata getIdPMetadata(String entityId) {
+        if (entityId != null && _idpMetadataMap.containsKey(entityId)) {
+            return _idpMetadataMap.get(entityId);
+        }
+        String defaultIdpId = SAMLDefaultIdentityProviderId.value();
+        if (defaultIdpId != null && _idpMetadataMap.containsKey(defaultIdpId)) {
+            return _idpMetadataMap.get(defaultIdpId);
+        }
+        // In case of a single IdP, return that as default
+        if (_idpMetadataMap.size() == 1) {
+            return _idpMetadataMap.values().iterator().next();
+        }
+        return null;
     }
 
-    public X509Certificate getIdpEncryptionKey() {
-        return idpEncryptionKey;
+    @Override
+    public Collection<SAMLProviderMetadata> getAllIdPMetadata() {
+        return _idpMetadataMap.values();
     }
 
     public Boolean isSAMLPluginEnabled() {
         return SAMLIsPluginEnabled.value();
     }
 
-    public X509Certificate getSpX509Certificate() {
-        return spX509Key;
-    }
-
-    @Override
-    public KeyPair getSpKeyPair() {
-        return spKeyPair;
-    }
-
     @Override
     public String getConfigComponentName() {
         return "SAML2-PLUGIN";
@@ -291,8 +376,14 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[]{SAMLIsPluginEnabled, SAMLUserAttributeName, SAMLCloudStackRedirectionUrl,
+        return new ConfigKey<?>[] {
+                SAMLIsPluginEnabled, SAMLServiceProviderID,
+                SAMLServiceProviderContactPersonName, SAMLServiceProviderContactEmail,
+                SAMLServiceProviderOrgName, SAMLServiceProviderOrgUrl,
                 SAMLServiceProviderSingleSignOnURL, SAMLServiceProviderSingleLogOutURL,
-                SAMLServiceProviderID, SAMLIdentityProviderMetadataURL, SAMLIdentityProviderId, SAMLTimeout};
+                SAMLCloudStackRedirectionUrl, SAMLUserAttributeName,
+                SAMLDefaultDomain,
+                SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId,
+                SAMLTimeout};
     }
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
index de2481e..92a17a9 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
@@ -63,8 +63,8 @@ public class SAML2UserAuthenticator extends DefaultUserAuthenticator {
             return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
         } else {
             User user = _userDao.getUser(userAccount.getId());
-            if (user != null && requestParameters != null && requestParameters.containsKey(SAMLUtils.SAML_RESPONSE)) {
-                final String samlResponse = ((String[])requestParameters.get(SAMLUtils.SAML_RESPONSE))[0];
+            if (user != null && requestParameters != null && requestParameters.containsKey(SAMLPluginConstants.SAML_RESPONSE)) {
+                final String samlResponse = ((String[])requestParameters.get(SAMLPluginConstants.SAML_RESPONSE))[0];
                 Response responseObject = null;
                 try {
                     DefaultBootstrap.bootstrap();

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLPluginConstants.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLPluginConstants.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLPluginConstants.java
new file mode 100644
index 0000000..4745915
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLPluginConstants.java
@@ -0,0 +1,27 @@
+//
+// 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.saml;
+
+public class SAMLPluginConstants {
+    public static final String SAML_RESPONSE = "SAMLResponse";
+    public static final String SAML_IDPID = "SAML_IDPID";
+    public static final String SAML_SESSION = "SAML_SESSION";
+    public static final String SAMLSP_KEYPAIR = "SAMLSP_KEYPAIR";
+    public static final String SAMLSP_X509CERT = "SAMLSP_X509CERT";
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLProviderMetadata.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLProviderMetadata.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLProviderMetadata.java
new file mode 100644
index 0000000..c7138a1
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLProviderMetadata.java
@@ -0,0 +1,122 @@
+// 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.saml;
+
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+
+public class SAMLProviderMetadata {
+    private String entityId;
+    private String organizationName;
+    private String organizationUrl;
+    private String contactPersonName;
+    private String contactPersonEmail;
+    private String ssoUrl;
+    private String sloUrl;
+    private KeyPair keyPair;
+    private X509Certificate signingCertificate;
+    private X509Certificate encryptionCertificate;
+
+    public SAMLProviderMetadata() {
+    }
+
+    public void setCommonCertificate(X509Certificate certificate) {
+        this.signingCertificate = certificate;
+        this.encryptionCertificate = certificate;
+    }
+
+    public String getEntityId() {
+        return entityId;
+    }
+
+    public void setEntityId(String entityId) {
+        this.entityId = entityId;
+    }
+
+    public String getContactPersonName() {
+        return contactPersonName;
+    }
+
+    public void setContactPersonName(String contactPersonName) {
+        this.contactPersonName = contactPersonName;
+    }
+
+    public String getContactPersonEmail() {
+        return contactPersonEmail;
+    }
+
+    public void setContactPersonEmail(String contactPersonEmail) {
+        this.contactPersonEmail = contactPersonEmail;
+    }
+
+    public String getOrganizationName() {
+        return organizationName;
+    }
+
+    public void setOrganizationName(String organizationName) {
+        this.organizationName = organizationName;
+    }
+
+    public String getOrganizationUrl() {
+        return organizationUrl;
+    }
+
+    public void setOrganizationUrl(String organizationUrl) {
+        this.organizationUrl = organizationUrl;
+    }
+
+    public KeyPair getKeyPair() {
+        return keyPair;
+    }
+
+    public void setKeyPair(KeyPair keyPair) {
+        this.keyPair = keyPair;
+    }
+
+    public X509Certificate getSigningCertificate() {
+        return signingCertificate;
+    }
+
+    public void setSigningCertificate(X509Certificate signingCertificate) {
+        this.signingCertificate = signingCertificate;
+    }
+
+    public X509Certificate getEncryptionCertificate() {
+        return encryptionCertificate;
+    }
+
+    public void setEncryptionCertificate(X509Certificate encryptionCertificate) {
+        this.encryptionCertificate = encryptionCertificate;
+    }
+
+    public String getSsoUrl() {
+        return ssoUrl;
+    }
+
+    public void setSsoUrl(String ssoUrl) {
+        this.ssoUrl = ssoUrl;
+    }
+
+    public String getSloUrl() {
+        return sloUrl;
+    }
+
+    public void setSloUrl(String sloUrl) {
+        this.sloUrl = sloUrl;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
index 95cbefc..e2a9dbc 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
@@ -26,6 +26,7 @@ import org.bouncycastle.x509.X509V1CertificateGenerator;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.opensaml.Configuration;
+import org.opensaml.DefaultBootstrap;
 import org.opensaml.common.SAMLVersion;
 import org.opensaml.common.xml.SAMLConstants;
 import org.opensaml.saml2.core.Assertion;
@@ -37,17 +38,13 @@ import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
 import org.opensaml.saml2.core.AuthnRequest;
 import org.opensaml.saml2.core.Issuer;
 import org.opensaml.saml2.core.LogoutRequest;
-import org.opensaml.saml2.core.NameID;
 import org.opensaml.saml2.core.RequestedAuthnContext;
 import org.opensaml.saml2.core.Response;
-import org.opensaml.saml2.core.SessionIndex;
 import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
 import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
 import org.opensaml.saml2.core.impl.IssuerBuilder;
 import org.opensaml.saml2.core.impl.LogoutRequestBuilder;
-import org.opensaml.saml2.core.impl.NameIDBuilder;
 import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
-import org.opensaml.saml2.core.impl.SessionIndexBuilder;
 import org.opensaml.xml.ConfigurationException;
 import org.opensaml.xml.XMLObject;
 import org.opensaml.xml.io.Marshaller;
@@ -66,6 +63,7 @@ import javax.security.auth.x500.X500Principal;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.FactoryConfigurationError;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -97,12 +95,6 @@ import java.util.zip.DeflaterOutputStream;
 public class SAMLUtils {
     public static final Logger s_logger = Logger.getLogger(SAMLUtils.class);
 
-    public static final String SAML_RESPONSE = "SAMLResponse";
-    public static final String SAML_NAMEID = "SAML_NAMEID";
-    public static final String SAML_SESSION = "SAML_SESSION";
-    public static final String SAMLSP_KEYPAIR = "SAMLSP_KEYPAIR";
-    public static final String SAMLSP_X509CERT = "SAMLSP_X509CERT";
-
     public static String generateSecureRandomId() {
         return new BigInteger(160, new SecureRandom()).toString(32);
     }
@@ -141,6 +133,22 @@ public class SAMLUtils {
         return null;
     }
 
+    public static String buildAuthnRequestUrl(SAMLProviderMetadata spMetadata, SAMLProviderMetadata idpMetadata) {
+        String redirectUrl = "";
+        try {
+            DefaultBootstrap.bootstrap();
+            AuthnRequest authnRequest = SAMLUtils.buildAuthnRequestObject(spMetadata.getEntityId(), idpMetadata.getSsoUrl(), spMetadata.getSsoUrl());
+            PrivateKey privateKey = null;
+            if (spMetadata.getKeyPair() != null) {
+                privateKey = spMetadata.getKeyPair().getPrivate();
+            }
+            redirectUrl = idpMetadata.getSsoUrl() + "?" + SAMLUtils.generateSAMLRequestSignature("SAMLRequest=" + SAMLUtils.encodeSAMLRequest(authnRequest), privateKey);
+        } catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException | NoSuchAlgorithmException | InvalidKeyException | java.security.SignatureException e) {
+            s_logger.error("SAML AuthnRequest message building error: " + e.getMessage());
+        }
+        return redirectUrl;
+    }
+
     public static AuthnRequest buildAuthnRequestObject(String spId, String idpUrl, String consumerUrl) {
         String authnId = generateSecureRandomId();
         // Issuer object
@@ -179,26 +187,17 @@ public class SAMLUtils {
         return authnRequest;
     }
 
-    public static LogoutRequest buildLogoutRequest(String logoutUrl, String spId, NameID sessionNameId, String sessionIndex) {
+    public static LogoutRequest buildLogoutRequest(String logoutUrl, String spId) {
         IssuerBuilder issuerBuilder = new IssuerBuilder();
         Issuer issuer = issuerBuilder.buildObject();
         issuer.setValue(spId);
 
-        SessionIndex sessionIndexElement = new SessionIndexBuilder().buildObject();
-        sessionIndexElement.setSessionIndex(sessionIndex);
-
-        NameID nameID = new NameIDBuilder().buildObject();
-        nameID.setValue(sessionNameId.getValue());
-        nameID.setFormat(sessionNameId.getFormat());
-
         LogoutRequest logoutRequest = new LogoutRequestBuilder().buildObject();
         logoutRequest.setID(generateSecureRandomId());
         logoutRequest.setDestination(logoutUrl);
         logoutRequest.setVersion(SAMLVersion.VERSION_20);
         logoutRequest.setIssueInstant(new DateTime());
         logoutRequest.setIssuer(issuer);
-        logoutRequest.getSessionIndexes().add(sessionIndexElement);
-        logoutRequest.setNameID(nameID);
         return logoutRequest;
     }
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
index e6446a1..32d0fba 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
@@ -29,7 +29,6 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
 import javax.servlet.http.HttpServletRequest;
@@ -77,19 +76,9 @@ public class GetServiceProviderMetaDataCmdTest {
         String spId = "someSPID";
         String url = "someUrl";
         X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
-        Mockito.when(samlAuthManager.getServiceProviderId()).thenReturn(spId);
-        Mockito.when(samlAuthManager.getIdpSigningKey()).thenReturn(cert);
-        Mockito.when(samlAuthManager.getIdpSingleLogOutUrl()).thenReturn(url);
-        Mockito.when(samlAuthManager.getSpSingleLogOutUrl()).thenReturn(url);
 
         String result = cmd.authenticate("command", null, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
         Assert.assertTrue(result.contains("md:EntityDescriptor"));
-
-        Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getServiceProviderId();
-        Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getSpSingleSignOnUrl();
-        Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getSpSingleLogOutUrl();
-        Mockito.verify(samlAuthManager, Mockito.never()).getIdpSingleSignOnUrl();
-        Mockito.verify(samlAuthManager, Mockito.never()).getIdpSingleLogOutUrl();
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java
index 239bdcc..5b37388 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java
@@ -25,8 +25,8 @@ import com.cloud.user.UserVO;
 import com.cloud.user.dao.UserAccountDao;
 import com.cloud.user.dao.UserDao;
 import com.cloud.utils.Pair;
+import org.apache.cloudstack.saml.SAMLPluginConstants;
 import org.apache.cloudstack.saml.SAML2UserAuthenticator;
-import org.apache.cloudstack.saml.SAMLUtils;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -79,7 +79,7 @@ public class SAML2UserAuthenticatorTest {
         Assert.assertFalse(pair.first());
 
         // When there is SAMLRequest in params and user is same as the mocked one
-        params.put(SAMLUtils.SAML_RESPONSE, new String[]{"RandomString"});
+        params.put(SAMLPluginConstants.SAML_RESPONSE, new String[]{"RandomString"});
         pair = authenticator.authenticate("someUID", "random", 1l, params);
         Assert.assertFalse(pair.first());
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
index cef5c84..20def87 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
@@ -24,8 +24,6 @@ import org.apache.cloudstack.saml.SAMLUtils;
 import org.junit.Test;
 import org.opensaml.saml2.core.AuthnRequest;
 import org.opensaml.saml2.core.LogoutRequest;
-import org.opensaml.saml2.core.NameID;
-import org.opensaml.saml2.core.impl.NameIDBuilder;
 
 import java.security.KeyPair;
 import java.security.PrivateKey;
@@ -54,13 +52,9 @@ public class SAMLUtilsTest extends TestCase {
         String logoutUrl = "http://logoutUrl";
         String spId = "cloudstack";
         String sessionIndex = "12345";
-        String nameIdString = "someNameID";
-        NameID sessionNameId = new NameIDBuilder().buildObject();
-        sessionNameId.setValue(nameIdString);
-        LogoutRequest req = SAMLUtils.buildLogoutRequest(logoutUrl, spId, sessionNameId,  sessionIndex);
+        LogoutRequest req = SAMLUtils.buildLogoutRequest(logoutUrl, spId);
         assertEquals(req.getDestination(), logoutUrl);
         assertEquals(req.getIssuer().getValue(), spId);
-        assertEquals(req.getNameID().getValue(), nameIdString);
         assertEquals(req.getSessionIndexes().get(0).getSessionIndex(), sessionIndex);
     }
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
index 9170109..c2d4960 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
@@ -30,6 +30,7 @@ import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.auth.APIAuthenticationType;
 import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLPluginConstants;
 import org.apache.cloudstack.saml.SAMLUtils;
 import org.joda.time.DateTime;
 import org.junit.Assert;
@@ -148,10 +149,6 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
         String spId = "someSPID";
         String url = "someUrl";
         X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
-        Mockito.when(samlAuthManager.getServiceProviderId()).thenReturn(spId);
-        Mockito.when(samlAuthManager.getIdpSigningKey()).thenReturn(null);
-        Mockito.when(samlAuthManager.getIdpSingleSignOnUrl()).thenReturn(url);
-        Mockito.when(samlAuthManager.getSpSingleSignOnUrl()).thenReturn(url);
 
         Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null);
 
@@ -169,7 +166,7 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
         Mockito.verify(resp, Mockito.times(1)).sendRedirect(Mockito.anyString());
 
         // SSO SAMLResponse verification test, this should throw ServerApiException for auth failure
-        params.put(SAMLUtils.SAML_RESPONSE, new String[]{"Some String"});
+        params.put(SAMLPluginConstants.SAML_RESPONSE, new String[]{"Some String"});
         Mockito.stub(cmd.processSAMLResponse(Mockito.anyString())).toReturn(buildMockResponse());
         try {
             cmd.authenticate("command", params, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
index 8863e50..eff4b29 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
@@ -22,7 +22,6 @@ package org.apache.cloudstack.api.command;
 import com.cloud.utils.HttpUtils;
 import org.apache.cloudstack.api.ApiServerService;
 import org.apache.cloudstack.api.auth.APIAuthenticationType;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.saml.SAML2AuthManager;
 import org.apache.cloudstack.saml.SAMLUtils;
 import org.junit.Assert;
@@ -48,9 +47,6 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
     SAML2AuthManager samlAuthManager;
 
     @Mock
-    ConfigurationDao configDao;
-
-    @Mock
     HttpSession session;
 
     @Mock
@@ -71,19 +67,10 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
         managerField.setAccessible(true);
         managerField.set(cmd, samlAuthManager);
 
-        Field configDaoField = SAML2LogoutAPIAuthenticatorCmd.class.getDeclaredField("_configDao");
-        configDaoField.setAccessible(true);
-        configDaoField.set(cmd, configDao);
-
         String spId = "someSPID";
         String url = "someUrl";
         X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
-        Mockito.when(samlAuthManager.getServiceProviderId()).thenReturn(spId);
-        Mockito.when(samlAuthManager.getIdpSigningKey()).thenReturn(cert);
-        Mockito.when(samlAuthManager.getIdpSingleLogOutUrl()).thenReturn(url);
-        Mockito.when(samlAuthManager.getSpSingleLogOutUrl()).thenReturn(url);
         Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null);
-        Mockito.when(configDao.getValue(Mockito.anyString())).thenReturn("someString");
 
         cmd.authenticate("command", null, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
         Mockito.verify(resp, Mockito.times(1)).sendRedirect(Mockito.anyString());

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/ui/css/cloudstack3.css
----------------------------------------------------------------------
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 0a2c57f..8e04ad0 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -369,7 +369,7 @@ body.login {
 .login .select-language select {
   width: 260px;
   border: 1px solid #808080;
-  margin-top: 30px;
+  margin-top: 20px;
   /*+border-radius:4px;*/
   -moz-border-radius: 4px;
   -webkit-border-radius: 4px;