cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bhais...@apache.org
Subject [2/5] git commit: updated refs/heads/master to 3ff92e8
Date Mon, 29 Jun 2015 10:59:36 GMT
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/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 17a0a30..817e62c 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
@@ -17,7 +17,6 @@
 package org.apache.cloudstack.api.command;
 
 import com.cloud.api.response.ApiResponseSerializer;
-import com.cloud.configuration.Config;
 import com.cloud.user.Account;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiErrorCode;
@@ -28,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.utils.auth.SAMLUtils;
+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;
@@ -60,8 +59,7 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
 
     @Inject
     ApiServerService _apiServer;
-    @Inject
-    ConfigurationDao _configDao;
+
     SAML2AuthManager _samlAuthManager;
 
     /////////////////////////////////////////////////////
@@ -94,7 +92,7 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
 
         if (session == null) {
             try {
-                resp.sendRedirect(_configDao.getValue(Config.SAMLCloudStackRedirectionUrl.key()));
+                resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value());
             } catch (IOException ignored) {
             }
             return responseString;
@@ -111,7 +109,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)) {
@@ -123,25 +121,26 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
                 s_logger.error("SAMLResponse processing error: " + e.getMessage());
             }
             try {
-                resp.sendRedirect(_configDao.getValue(Config.SAMLCloudStackRedirectionUrl.key()));
+                resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value());
             } catch (IOException ignored) {
             }
             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);
+        String nameId = (String) session.getAttribute(SAMLPluginConstants.SAML_NAMEID);
+        if (idpMetadata == null || nameId == null || nameId.isEmpty()) {
             try {
-                resp.sendRedirect(_configDao.getValue(Config.SAMLCloudStackRedirectionUrl.key()));
+                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(), nameId);
 
         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/107595a6/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/107595a6/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SamlAuthorizationResponse.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SamlAuthorizationResponse.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SamlAuthorizationResponse.java
new file mode 100644
index 0000000..445ee88
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SamlAuthorizationResponse.java
@@ -0,0 +1,68 @@
+// 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.cloud.user.User;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+
+@EntityReference(value = User.class)
+public class SamlAuthorizationResponse extends BaseResponse {
+    @SerializedName("userid")
+    @Param(description = "the user ID")
+    private String userId;
+
+    @SerializedName("status")
+    @Param(description = "the SAML authorization status")
+    private Boolean status;
+
+    @SerializedName("idpid")
+    @Param(description = "the authorized Identity Provider ID")
+    private String idpId;
+
+    public SamlAuthorizationResponse(String userId, Boolean status, String idpId) {
+        this.userId = userId;
+        this.status = status;
+        this.idpId = idpId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public Boolean getStatus() {
+        return status;
+    }
+
+    public void setStatus(Boolean status) {
+        this.status = status;
+    }
+
+    public String getIdpId() {
+        return idpId;
+    }
+
+    public void setIdpId(String idpId) {
+        this.idpId = idpId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/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 9c0d4b4..fc9a6db 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
@@ -17,23 +17,64 @@
 
 package org.apache.cloudstack.saml;
 
+import com.cloud.utils.component.PluggableService;
 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 String getServiceProviderId();
-    public String getIdentityProviderId();
+public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableService {
 
-    public X509Certificate getIdpSigningKey();
-    public X509Certificate getIdpEncryptionKey();
-    public X509Certificate getSpX509Certificate();
-    public KeyPair getSpKeyPair();
+    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 String getSpSingleSignOnUrl();
-    public String getIdpSingleSignOnUrl();
+    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 String getSpSingleLogOutUrl();
-    public String getIdpSingleLogOutUrl();
+    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> 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);
+
+    public static final ConfigKey<String> SAMLServiceProviderSingleLogOutURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.slo.url", "http://localhost:8080/client/",
+            "SAML2 CloudStack Service Provider Single Log Out URL", 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> 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> 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<String> SAMLSignatureAlgorithm = new ConfigKey<String>("Advanced", String.class, "saml2.sigalg", "SHA1",
+            "The algorithm to use to when signing a SAML request. Default is SHA1, allowed algorithms: SHA1, SHA256, SHA384, SHA512", true);
+
+    public static final ConfigKey<Integer> SAMLTimeout = new ConfigKey<Integer>("Advanced", Integer.class, "saml2.timeout", "1800",
+            "SAML2 IDP Metadata refresh interval in seconds, minimum value is set to 300", true);
+
+    public SAMLProviderMetadata getSPMetadata();
+    public SAMLProviderMetadata getIdPMetadata(String entityId);
+    public Collection<SAMLProviderMetadata> getAllIdPMetadata();
+
+    public boolean isUserAuthorized(Long userId, String entityId);
+    public boolean authorizeUser(Long userId, String entityId, boolean enable);
+
+    public void saveToken(String authnId, String domain, String entity);
+    public SAMLTokenVO getToken(String authnId);
+    public void expireTokens();
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/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 36c9da5..185955c 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
@@ -16,28 +16,46 @@
 // under the License.
 package org.apache.cloudstack.saml;
 
-import com.cloud.configuration.Config;
+import com.cloud.domain.Domain;
+import com.cloud.user.DomainManager;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.PropertiesUtil;
 import com.cloud.utils.component.AdapterBase;
 import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.api.command.AuthorizeSAMLSSOCmd;
 import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
+import org.apache.cloudstack.api.command.ListIdpsCmd;
+import org.apache.cloudstack.api.command.ListSamlAuthorizationCmd;
 import org.apache.cloudstack.api.command.SAML2LoginAPIAuthenticatorCmd;
 import org.apache.cloudstack.api.command.SAML2LogoutAPIAuthenticatorCmd;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
 import org.apache.cloudstack.framework.security.keystore.KeystoreVO;
-import org.apache.cloudstack.utils.auth.SAMLUtils;
-import org.apache.log4j.Logger;
 import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.httpclient.HttpClient;
+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.AbstractReloadingMetadataProvider;
+import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider;
 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;
@@ -48,6 +66,7 @@ import javax.inject.Inject;
 import javax.xml.stream.FactoryConfigurationError;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutput;
@@ -63,61 +82,87 @@ 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;
+import java.util.Timer;
+import java.util.TimerTask;
 
 @Component
 @Local(value = {SAML2AuthManager.class, PluggableAPIAuthenticator.class})
-public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManager {
+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;
+    private Timer _timer;
+    private int _refreshInterval = SAMLPluginConstants.SAML_REFRESH_INTERVAL;
+    private AbstractReloadingMetadataProvider _idpMetaDataProvider;
 
     @Inject
-    ConfigurationDao _configDao;
+    private KeystoreDao _ksDao;
 
     @Inject
-    private KeystoreDao _ksDao;
+    private SAMLTokenDao _samlTokenDao;
+
+    @Inject
+    private UserDao _userDao;
+
+    @Inject
+    DomainManager _domainMgr;
 
     @Override
     public boolean start() {
         if (isSAMLPluginEnabled()) {
             setup();
+            s_logger.info("SAML auth plugin loaded");
+        } else {
+            s_logger.info("SAML auth plugin not enabled so not loading");
         }
         return super.start();
     }
 
-    private boolean setup() {
-        KeystoreVO keyStoreVO = _ksDao.findByName(SAMLUtils.SAMLSP_KEYPAIR);
+    @Override
+    public boolean stop() {
+        if (_timer != null) {
+            _timer.cancel();
+        }
+        return super.stop();
+    }
+
+    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);
@@ -125,7 +170,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");
@@ -142,61 +187,194 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
                 }
             }
         }
-
-        this.serviceProviderId = _configDao.getValue(Config.SAMLServiceProviderID.key());
-        this.identityProviderId = _configDao.getValue(Config.SAMLIdentityProviderID.key());
-
-        this.spSingleSignOnUrl = _configDao.getValue(Config.SAMLServiceProviderSingleSignOnURL.key());
-        this.spSingleLogOutUrl = _configDao.getValue(Config.SAMLServiceProviderSingleLogOutURL.key());
-
-        String idpMetaDataUrl = _configDao.getValue(Config.SAMLIdentityProviderMetadataURL.key());
-
-        int tolerance = 30000;
-        String timeout = _configDao.getValue(Config.SAMLTimeout.key());
-        if (timeout != null) {
-            tolerance = Integer.parseInt(timeout);
+        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;
+    }
 
-        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());
                     }
                 }
+            }
 
-                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) {
+                        }
+                    }
+                    if (kd.getUse() == UsageType.UNSPECIFIED) {
+                        try {
+                            unspecifiedKey = KeyInfoHelper.getCertificates(kd.getKeyInfo()).get(0);
                         } catch (CertificateException ignored) {
                         }
                     }
                 }
+            }
+            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 (entitiesDescriptor.getEntitiesDescriptors() != null) {
+                for (EntitiesDescriptor entitiesDescriptorInner: entitiesDescriptor.getEntitiesDescriptors()) {
+                    discoverAndAddIdp(entitiesDescriptorInner, idpMap);
+                }
+            }
+        }
+    }
+
+    class MetadataRefreshTask extends TimerTask {
+        @Override
+        public void run() {
+            if (_idpMetaDataProvider == null) {
+                return;
+            }
+            s_logger.debug("Starting SAML IDP Metadata Refresh Task");
+            Map <String, SAMLProviderMetadata> metadataMap = new HashMap<String, SAMLProviderMetadata>();
+            try {
+                discoverAndAddIdp(_idpMetaDataProvider.getMetadata(), metadataMap);
+                _idpMetadataMap = metadataMap;
+                expireTokens();
+                s_logger.debug("Finished refreshing SAML Metadata and expiring old auth tokens");
+            } catch (MetadataProviderException e) {
+                s_logger.warn("SAML Metadata Refresh task failed with exception: " + e.getMessage());
+            }
+
+        }
+    }
+
+    private boolean setup() {
+        if (!initSP()) {
+            s_logger.error("SAML Plugin failed to initialize, please fix the configuration and restart management server");
+            return false;
+        }
+        _timer = new Timer();
+        final HttpClient client = new HttpClient();
+        final String idpMetaDataUrl = SAMLIdentityProviderMetadataURL.value();
+        if (SAMLTimeout.value() != null && SAMLTimeout.value() > SAMLPluginConstants.SAML_REFRESH_INTERVAL) {
+            _refreshInterval = SAMLTimeout.value();
+        }
+        try {
+            DefaultBootstrap.bootstrap();
+            if (idpMetaDataUrl.startsWith("http")) {
+                _idpMetaDataProvider = new HTTPMetadataProvider(_timer, client, idpMetaDataUrl);
             } else {
-                s_logger.warn("Provided IDP XML Metadata does not contain IDPSSODescriptor, SAML authentication may not work");
+                File metadataFile = PropertiesUtil.findConfigFile(idpMetaDataUrl);
+                s_logger.debug("Provided Metadata is not a URL, trying to read metadata file from local path: " + metadataFile.getAbsolutePath());
+                _idpMetaDataProvider = new FilesystemMetadataProvider(_timer, metadataFile);
             }
+            _idpMetaDataProvider.setRequireValidMetadata(true);
+            _idpMetaDataProvider.setParserPool(new BasicParserPool());
+            _idpMetaDataProvider.initialize();
+            _timer.scheduleAtFixedRate(new MetadataRefreshTask(), 0, _refreshInterval * 1000);
         } catch (MetadataProviderException e) {
             s_logger.error("Unable to read SAML2 IDP MetaData URL, error:" + e.getMessage());
             s_logger.error("SAML2 Authentication may be unavailable");
@@ -204,70 +382,138 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
             s_logger.error("OpenSAML bootstrapping failed: error: " + e.getMessage());
         } catch (NullPointerException e) {
             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");
+                    " please check the SAML global settings: " + e.getMessage());
         }
-
         return true;
     }
 
     @Override
-    public List<Class<?>> getAuthCommands() {
-        List<Class<?>> cmdList = new ArrayList<Class<?>>();
-        if (!isSAMLPluginEnabled()) {
-            return cmdList;
+    public SAMLProviderMetadata getSPMetadata() {
+        return _spMetadata;
+    }
+
+    @Override
+    public SAMLProviderMetadata getIdPMetadata(String entityId) {
+        if (entityId != null && _idpMetadataMap.containsKey(entityId)) {
+            return _idpMetadataMap.get(entityId);
         }
-        cmdList.add(SAML2LoginAPIAuthenticatorCmd.class);
-        cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class);
-        cmdList.add(GetServiceProviderMetaDataCmd.class);
-        return cmdList;
+        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 String getServiceProviderId() {
-        return serviceProviderId;
+    @Override
+    public Collection<SAMLProviderMetadata> getAllIdPMetadata() {
+        return _idpMetadataMap.values();
     }
 
-    public String getIdpSingleSignOnUrl() {
-        return this.idpSingleSignOnUrl;
+    @Override
+    public boolean isUserAuthorized(Long userId, String entityId) {
+        UserVO user = _userDao.getUser(userId);
+        if (user != null) {
+            if (user.getSource().equals(User.Source.SAML2) &&
+                    user.getExternalEntity().equalsIgnoreCase(entityId)) {
+                return true;
+            }
+        }
+        return false;
     }
 
-    public String getIdpSingleLogOutUrl() {
-        return this.idpSingleLogOutUrl;
+    @Override
+    public boolean authorizeUser(Long userId, String entityId, boolean enable) {
+        UserVO user = _userDao.getUser(userId);
+        if (user != null) {
+            if (enable) {
+                user.setExternalEntity(entityId);
+                user.setSource(User.Source.SAML2);
+            } else {
+                if (user.getSource().equals(User.Source.SAML2)) {
+                    user.setSource(User.Source.SAML2DISABLED);
+                } else {
+                    return false;
+                }
+            }
+            _userDao.update(user.getId(), user);
+            return true;
+        }
+        return false;
     }
 
-    public String getSpSingleSignOnUrl() {
-        return spSingleSignOnUrl;
+    @Override
+    public void saveToken(String authnId, String domainPath, String entity) {
+        Long domainId = null;
+        if (domainPath != null) {
+            Domain domain = _domainMgr.findDomainByPath(domainPath);
+            if (domain != null) {
+                domainId = domain.getId();
+            }
+        }
+        SAMLTokenVO token = new SAMLTokenVO(authnId, domainId, entity);
+        if (_samlTokenDao.findByUuid(authnId) == null) {
+            _samlTokenDao.persist(token);
+        } else {
+            s_logger.warn("Duplicate SAML token for entity=" + entity + " token id=" + authnId + " domain=" + domainPath);
+        }
     }
 
-    public String getSpSingleLogOutUrl() {
-        return spSingleLogOutUrl;
+    @Override
+    public SAMLTokenVO getToken(String authnId) {
+        return _samlTokenDao.findByUuid(authnId);
     }
 
-    public String getIdentityProviderId() {
-        return identityProviderId;
+    @Override
+    public void expireTokens() {
+        _samlTokenDao.expireTokens();
     }
 
-    public X509Certificate getIdpSigningKey() {
-        return idpSigningKey;
+    public Boolean isSAMLPluginEnabled() {
+        return SAMLIsPluginEnabled.value();
     }
 
-    public X509Certificate getIdpEncryptionKey() {
-        return idpEncryptionKey;
+    @Override
+    public String getConfigComponentName() {
+        return "SAML2-PLUGIN";
     }
 
-    public Boolean isSAMLPluginEnabled() {
-        return Boolean.valueOf(_configDao.getValue(Config.SAMLIsPluginEnabled.key()));
+    @Override
+    public List<Class<?>> getAuthCommands() {
+        List<Class<?>> cmdList = new ArrayList<Class<?>>();
+        if (!isSAMLPluginEnabled()) {
+            return cmdList;
+        }
+        cmdList.add(SAML2LoginAPIAuthenticatorCmd.class);
+        cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class);
+        cmdList.add(GetServiceProviderMetaDataCmd.class);
+        cmdList.add(ListIdpsCmd.class);
+        return cmdList;
     }
 
-    public X509Certificate getSpX509Certificate() {
-        return spX509Key;
+    @Override
+    public List<Class<?>> getCommands() {
+        List<Class<?>> cmdList = new ArrayList<Class<?>>();
+        if (!isSAMLPluginEnabled()) {
+            return cmdList;
+        }
+        cmdList.add(AuthorizeSAMLSSOCmd.class);
+        cmdList.add(ListSamlAuthorizationCmd.class);
+        return cmdList;
     }
 
     @Override
-    public KeyPair getSpKeyPair() {
-        return spKeyPair;
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[] {
+                SAMLIsPluginEnabled, SAMLServiceProviderID,
+                SAMLServiceProviderContactPersonName, SAMLServiceProviderContactEmail,
+                SAMLServiceProviderOrgName, SAMLServiceProviderOrgUrl,
+                SAMLServiceProviderSingleSignOnURL, SAMLServiceProviderSingleLogOutURL,
+                SAMLCloudStackRedirectionUrl, SAMLUserAttributeName,
+                SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId,
+                SAMLSignatureAlgorithm, SAMLTimeout};
     }
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/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 68bd81c..5c8a390 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
@@ -21,12 +21,20 @@ import com.cloud.user.UserAccount;
 import com.cloud.user.dao.UserAccountDao;
 import com.cloud.user.dao.UserDao;
 import com.cloud.utils.Pair;
-import org.apache.cloudstack.utils.auth.SAMLUtils;
 import org.apache.cxf.common.util.StringUtils;
 import org.apache.log4j.Logger;
+import org.opensaml.DefaultBootstrap;
+import org.opensaml.saml2.core.Response;
+import org.opensaml.saml2.core.StatusCode;
+import org.opensaml.xml.ConfigurationException;
+import org.opensaml.xml.io.UnmarshallingException;
+import org.xml.sax.SAXException;
 
 import javax.ejb.Local;
 import javax.inject.Inject;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.FactoryConfigurationError;
+import java.io.IOException;
 import java.util.Map;
 
 @Local(value = {UserAuthenticator.class})
@@ -50,13 +58,23 @@ public class SAML2UserAuthenticator extends DefaultUserAuthenticator {
         }
 
         final UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
-        if (userAccount == null) {
-            s_logger.debug("Unable to find user with " + username + " in domain " + domainId);
+        if (userAccount == null || userAccount.getSource() != User.Source.SAML2) {
+            s_logger.debug("Unable to find user with " + username + " in domain " + domainId + ", or user source is not SAML2");
             return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
         } else {
             User user = _userDao.getUser(userAccount.getId());
-            if (user != null && SAMLUtils.checkSAMLUser(user.getUuid(), username) &&
-                    requestParameters != null && requestParameters.containsKey(SAMLUtils.SAML_RESPONSE)) {
+            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();
+                    responseObject = SAMLUtils.decodeSAMLResponse(samlResponse);
+                } catch (ConfigurationException | FactoryConfigurationError | ParserConfigurationException | SAXException | IOException | UnmarshallingException e) {
+                    return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
+                }
+                if (!responseObject.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI)) {
+                    return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
+                }
                 return new Pair<Boolean, ActionOnFailedAuthentication>(true, null);
             }
         }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/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..5f806e2
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLPluginConstants.java
@@ -0,0 +1,30 @@
+//
+// 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 int SAML_REFRESH_INTERVAL = 300;
+
+    public static final String SAML_RESPONSE = "SAMLResponse";
+    public static final String SAML_IDPID = "SAML_IDPID";
+    public static final String SAML_SESSIONID = "SAML_SESSIONID";
+    public static final String SAML_NAMEID = "SAML_NAMEID";
+    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/107595a6/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/107595a6/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDao.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDao.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDao.java
new file mode 100644
index 0000000..b045562
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDao.java
@@ -0,0 +1,23 @@
+// 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 com.cloud.utils.db.GenericDao;
+
+public interface SAMLTokenDao extends GenericDao<SAMLTokenVO, Long> {
+    public void expireTokens();
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDaoImpl.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDaoImpl.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDaoImpl.java
new file mode 100644
index 0000000..eb106d9
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDaoImpl.java
@@ -0,0 +1,51 @@
+// 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 com.cloud.utils.db.DB;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.TransactionLegacy;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.springframework.stereotype.Component;
+
+import javax.ejb.Local;
+import java.sql.PreparedStatement;
+
+@DB
+@Component
+@Local(value = {SAMLTokenDao.class})
+public class SAMLTokenDaoImpl extends GenericDaoBase<SAMLTokenVO, Long> implements SAMLTokenDao {
+
+    public SAMLTokenDaoImpl() {
+        super();
+    }
+
+    @Override
+    public void expireTokens() {
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        try {
+            txn.start();
+            String sql = "DELETE FROM `saml_token` WHERE `created` < (NOW() - INTERVAL 1 HOUR)";
+            PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql);
+            pstmt.executeUpdate();
+            txn.commit();
+        } catch (Exception e) {
+            txn.rollback();
+            throw new CloudRuntimeException("Unable to flush old SAML tokens due to exception", e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenVO.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenVO.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenVO.java
new file mode 100644
index 0000000..c8ac2f1
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenVO.java
@@ -0,0 +1,97 @@
+// 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 com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+
+@Entity
+@Table(name = "saml_token")
+public class SAMLTokenVO implements Identity, InternalIdentity {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private long id;
+
+    @Column(name = "uuid")
+    private String uuid;
+
+    @Column(name = "domain_id")
+    private Long domainId = null;
+
+    @Column(name = "entity")
+    private String entity = null;
+
+    @Column(name = GenericDao.CREATED_COLUMN)
+    private Date created;
+
+    public SAMLTokenVO() {
+    }
+
+    public SAMLTokenVO(String uuid, Long domainId, String entity) {
+        this.uuid = uuid;
+        this.domainId = domainId;
+        this.entity = entity;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public Long getDomainId() {
+        return domainId;
+    }
+
+    public void setDomainId(long domainId) {
+        this.domainId = domainId;
+    }
+
+    public String getEntity() {
+        return entity;
+    }
+
+    public void setEntity(String entity) {
+        this.entity = entity;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/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
new file mode 100644
index 0000000..0216ad7
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
@@ -0,0 +1,354 @@
+//
+// 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 com.cloud.utils.HttpUtils;
+import org.apache.log4j.Logger;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+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;
+import org.opensaml.saml2.core.Attribute;
+import org.opensaml.saml2.core.AttributeStatement;
+import org.opensaml.saml2.core.AuthnContext;
+import org.opensaml.saml2.core.AuthnContextClassRef;
+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.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.xml.ConfigurationException;
+import org.opensaml.xml.XMLObject;
+import org.opensaml.xml.io.Marshaller;
+import org.opensaml.xml.io.MarshallingException;
+import org.opensaml.xml.io.Unmarshaller;
+import org.opensaml.xml.io.UnmarshallerFactory;
+import org.opensaml.xml.io.UnmarshallingException;
+import org.opensaml.xml.signature.SignatureConstants;
+import org.opensaml.xml.util.Base64;
+import org.opensaml.xml.util.XMLHelper;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+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;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.net.URLEncoder;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.List;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+public class SAMLUtils {
+    public static final Logger s_logger = Logger.getLogger(SAMLUtils.class);
+
+    public static String generateSecureRandomId() {
+        return new BigInteger(160, new SecureRandom()).toString(32);
+    }
+
+    public static String getValueFromAttributeStatements(final List<AttributeStatement> attributeStatements, final String attributeKey) {
+        if (attributeStatements == null || attributeStatements.size() < 1 || attributeKey == null) {
+            return null;
+        }
+        for (AttributeStatement attributeStatement : attributeStatements) {
+            if (attributeStatement == null || attributeStatements.size() < 1) {
+                continue;
+            }
+            for (Attribute attribute : attributeStatement.getAttributes()) {
+                if (attribute.getAttributeValues() != null && attribute.getAttributeValues().size() > 0) {
+                    String value = attribute.getAttributeValues().get(0).getDOM().getTextContent();
+                    s_logger.debug("SAML attribute name: " + attribute.getName() + " friendly-name:" + attribute.getFriendlyName() + " value:" + value);
+                    if (attributeKey.equals(attribute.getName()) || attributeKey.equals(attribute.getFriendlyName())) {
+                        return value;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public static String getValueFromAssertions(final List<Assertion> assertions, final String attributeKey) {
+        if (assertions == null || attributeKey == null) {
+            return null;
+        }
+        for (Assertion assertion : assertions) {
+            String value = getValueFromAttributeStatements(assertion.getAttributeStatements(), attributeKey);
+            if (value != null) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+    public static String buildAuthnRequestUrl(final String authnId, final SAMLProviderMetadata spMetadata, final SAMLProviderMetadata idpMetadata, final String signatureAlgorithm) {
+        String redirectUrl = "";
+        try {
+            DefaultBootstrap.bootstrap();
+            AuthnRequest authnRequest = SAMLUtils.buildAuthnRequestObject(authnId, 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, signatureAlgorithm);
+        } 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(final String authnId, final String spId, final String idpUrl, final String consumerUrl) {
+        // Issuer object
+        IssuerBuilder issuerBuilder = new IssuerBuilder();
+        Issuer issuer = issuerBuilder.buildObject();
+        issuer.setValue(spId);
+
+        // AuthnContextClass
+        AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
+        AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject(
+                SAMLConstants.SAML20_NS,
+                "AuthnContextClassRef", "saml");
+        authnContextClassRef.setAuthnContextClassRef(AuthnContext.PPT_AUTHN_CTX);
+
+        // AuthnContext
+        RequestedAuthnContextBuilder requestedAuthnContextBuilder = new RequestedAuthnContextBuilder();
+        RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
+        requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
+        requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
+
+        // Creation of AuthRequestObject
+        AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
+        AuthnRequest authnRequest = authRequestBuilder.buildObject();
+        authnRequest.setID(authnId);
+        authnRequest.setDestination(idpUrl);
+        authnRequest.setVersion(SAMLVersion.VERSION_20);
+        authnRequest.setForceAuthn(false);
+        authnRequest.setIsPassive(false);
+        authnRequest.setIssueInstant(new DateTime());
+        authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
+        authnRequest.setAssertionConsumerServiceURL(consumerUrl);
+        authnRequest.setProviderName(spId);
+        authnRequest.setIssuer(issuer);
+        authnRequest.setRequestedAuthnContext(requestedAuthnContext);
+
+        return authnRequest;
+    }
+
+    public static LogoutRequest buildLogoutRequest(String logoutUrl, String spId, String nameIdString) {
+        Issuer issuer = new IssuerBuilder().buildObject();
+        issuer.setValue(spId);
+        NameID nameID = new NameIDBuilder().buildObject();
+        nameID.setValue(nameIdString);
+        LogoutRequest logoutRequest = new LogoutRequestBuilder().buildObject();
+        logoutRequest.setID(generateSecureRandomId());
+        logoutRequest.setDestination(logoutUrl);
+        logoutRequest.setVersion(SAMLVersion.VERSION_20);
+        logoutRequest.setIssueInstant(new DateTime());
+        logoutRequest.setIssuer(issuer);
+        logoutRequest.setNameID(nameID);
+        return logoutRequest;
+    }
+
+    public static String encodeSAMLRequest(XMLObject authnRequest)
+            throws MarshallingException, IOException {
+        Marshaller marshaller = Configuration.getMarshallerFactory()
+                .getMarshaller(authnRequest);
+        Element authDOM = marshaller.marshall(authnRequest);
+        StringWriter requestWriter = new StringWriter();
+        XMLHelper.writeNode(authDOM, requestWriter);
+        String requestMessage = requestWriter.toString();
+        Deflater deflater = new Deflater(Deflater.DEFLATED, true);
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
+        deflaterOutputStream.write(requestMessage.getBytes());
+        deflaterOutputStream.close();
+        String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(), Base64.DONT_BREAK_LINES);
+        encodedRequestMessage = URLEncoder.encode(encodedRequestMessage, HttpUtils.UTF_8).trim();
+        return encodedRequestMessage;
+    }
+
+    public static Response decodeSAMLResponse(String responseMessage)
+            throws ConfigurationException, ParserConfigurationException,
+            SAXException, IOException, UnmarshallingException {
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+        documentBuilderFactory.setNamespaceAware(true);
+        DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
+        byte[] base64DecodedResponse = Base64.decode(responseMessage);
+        Document document = docBuilder.parse(new ByteArrayInputStream(base64DecodedResponse));
+        Element element = document.getDocumentElement();
+        UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
+        Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
+        return (Response) unmarshaller.unmarshall(element);
+    }
+
+    public static String generateSAMLRequestSignature(final String urlEncodedString, final PrivateKey signingKey, final String sigAlgorithmName)
+            throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, UnsupportedEncodingException {
+        if (signingKey == null) {
+            return urlEncodedString;
+        }
+
+        String opensamlAlgoIdSignature = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1;
+        String javaSignatureAlgorithmName = "SHA1withRSA";
+
+        if (sigAlgorithmName.equalsIgnoreCase("SHA256")) {
+            opensamlAlgoIdSignature = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256;
+            javaSignatureAlgorithmName = "SHA256withRSA";
+        } else if (sigAlgorithmName.equalsIgnoreCase("SHA384")) {
+            opensamlAlgoIdSignature = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA384;
+            javaSignatureAlgorithmName = "SHA384withRSA";
+        } else if (sigAlgorithmName.equalsIgnoreCase("SHA512")) {
+            opensamlAlgoIdSignature = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512;
+            javaSignatureAlgorithmName = "SHA512withRSA";
+        }
+
+        String url = urlEncodedString + "&SigAlg=" + URLEncoder.encode(opensamlAlgoIdSignature, HttpUtils.UTF_8);
+        Signature signature = Signature.getInstance(javaSignatureAlgorithmName);
+        signature.initSign(signingKey);
+        signature.update(url.getBytes());
+        String signatureString = Base64.encodeBytes(signature.sign(), Base64.DONT_BREAK_LINES);
+        if (signatureString != null) {
+            return url + "&Signature=" + URLEncoder.encode(signatureString, HttpUtils.UTF_8);
+        }
+        return url;
+    }
+
+    public static KeyFactory getKeyFactory() {
+        KeyFactory keyFactory = null;
+        try {
+            Security.addProvider(new BouncyCastleProvider());
+            keyFactory = KeyFactory.getInstance("RSA", "BC");
+        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+            s_logger.error("Unable to create KeyFactory:" + e.getMessage());
+        }
+        return keyFactory;
+    }
+
+    public static String savePublicKey(PublicKey key) {
+        try {
+            KeyFactory keyFactory = SAMLUtils.getKeyFactory();
+            if (keyFactory == null) return null;
+            X509EncodedKeySpec spec = keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
+            return new String(org.bouncycastle.util.encoders.Base64.encode(spec.getEncoded()));
+        } catch (InvalidKeySpecException e) {
+            s_logger.error("Unable to create KeyFactory:" + e.getMessage());
+        }
+        return null;
+    }
+
+    public static String savePrivateKey(PrivateKey key) {
+        try {
+            KeyFactory keyFactory = SAMLUtils.getKeyFactory();
+            if (keyFactory == null) return null;
+            PKCS8EncodedKeySpec spec = keyFactory.getKeySpec(key,
+                    PKCS8EncodedKeySpec.class);
+            return new String(org.bouncycastle.util.encoders.Base64.encode(spec.getEncoded()));
+        } catch (InvalidKeySpecException e) {
+            s_logger.error("Unable to create KeyFactory:" + e.getMessage());
+        }
+        return null;
+    }
+
+    public static PublicKey loadPublicKey(String publicKey) {
+        byte[] sigBytes = org.bouncycastle.util.encoders.Base64.decode(publicKey);
+        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(sigBytes);
+        KeyFactory keyFact = SAMLUtils.getKeyFactory();
+        if (keyFact == null)
+            return null;
+        try {
+            return keyFact.generatePublic(x509KeySpec);
+        } catch (InvalidKeySpecException e) {
+            s_logger.error("Unable to create PrivateKey from privateKey string:" + e.getMessage());
+        }
+        return null;
+    }
+
+    public static PrivateKey loadPrivateKey(String privateKey) {
+        byte[] sigBytes = org.bouncycastle.util.encoders.Base64.decode(privateKey);
+        PKCS8EncodedKeySpec pkscs8KeySpec = new PKCS8EncodedKeySpec(sigBytes);
+        KeyFactory keyFact = SAMLUtils.getKeyFactory();
+        if (keyFact == null)
+            return null;
+        try {
+            return keyFact.generatePrivate(pkscs8KeySpec);
+        } catch (InvalidKeySpecException e) {
+            s_logger.error("Unable to create PrivateKey from privateKey string:" + e.getMessage());
+        }
+        return null;
+    }
+
+    public static KeyPair generateRandomKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException {
+        Security.addProvider(new BouncyCastleProvider());
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
+        keyPairGenerator.initialize(4096, new SecureRandom());
+        return keyPairGenerator.generateKeyPair();
+    }
+
+    public static X509Certificate generateRandomX509Certificate(KeyPair keyPair) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateEncodingException, SignatureException, InvalidKeyException {
+        DateTime now = DateTime.now(DateTimeZone.UTC);
+        X500Principal dnName = new X500Principal("CN=ApacheCloudStack");
+        X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+        certGen.setSubjectDN(dnName);
+        certGen.setIssuerDN(dnName);
+        certGen.setNotBefore(now.minusDays(1).toDate());
+        certGen.setNotAfter(now.plusYears(3).toDate());
+        certGen.setPublicKey(keyPair.getPublic());
+        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+        return certGen.generate(keyPair.getPrivate(), "BC");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/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
new file mode 100644
index 0000000..db1323f
--- /dev/null
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+import com.cloud.utils.HttpUtils;
+import org.apache.cloudstack.api.ApiServerService;
+import org.apache.cloudstack.api.auth.APIAuthenticationType;
+import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
+import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLProviderMetadata;
+import org.apache.cloudstack.saml.SAMLUtils;
+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;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.lang.reflect.Field;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GetServiceProviderMetaDataCmdTest {
+
+    @Mock
+    ApiServerService apiServer;
+
+    @Mock
+    SAML2AuthManager samlAuthManager;
+
+    @Mock
+    HttpSession session;
+
+    @Mock
+    HttpServletResponse resp;
+
+    @Mock
+    HttpServletRequest req;
+
+    @Test
+    public void testAuthenticate() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, CertificateParsingException, CertificateEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, UnknownHostException {
+        GetServiceProviderMetaDataCmd cmd = new GetServiceProviderMetaDataCmd();
+
+        Field apiServerField = GetServiceProviderMetaDataCmd.class.getDeclaredField("_apiServer");
+        apiServerField.setAccessible(true);
+        apiServerField.set(cmd, apiServer);
+
+        Field managerField = GetServiceProviderMetaDataCmd.class.getDeclaredField("_samlAuthManager");
+        managerField.setAccessible(true);
+        managerField.set(cmd, samlAuthManager);
+
+        String spId = "someSPID";
+        String url = "someUrl";
+        KeyPair kp = SAMLUtils.generateRandomKeyPair();
+        X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp);
+
+        SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata();
+        providerMetadata.setEntityId("random");
+        providerMetadata.setSigningCertificate(cert);
+        providerMetadata.setEncryptionCertificate(cert);
+        providerMetadata.setKeyPair(kp);
+        providerMetadata.setSsoUrl("http://test.local");
+        providerMetadata.setSloUrl("http://test.local");
+
+        Mockito.when(samlAuthManager.getSPMetadata()).thenReturn(providerMetadata);
+
+        String result = cmd.authenticate("command", null, session, InetAddress.getByName("127.0.0.1"), HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
+        Assert.assertTrue(result.contains("md:EntityDescriptor"));
+    }
+
+    @Test
+    public void testGetAPIType() {
+        Assert.assertTrue(new GetServiceProviderMetaDataCmd().getAPIType() == APIAuthenticationType.LOGIN_API);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/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 83792c6..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.utils.auth.SAMLUtils;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -68,8 +68,6 @@ public class SAML2UserAuthenticatorTest {
         account.setId(1L);
 
         UserVO user = new UserVO();
-        user.setUuid(SAMLUtils.createSAMLId("someUID"));
-
         Mockito.when(userAccountDao.getUserAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(account);
         Mockito.when(userDao.getUser(Mockito.anyLong())).thenReturn(user);
 
@@ -81,9 +79,9 @@ 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 Object[]{});
+        params.put(SAMLPluginConstants.SAML_RESPONSE, new String[]{"RandomString"});
         pair = authenticator.authenticate("someUID", "random", 1l, params);
-        Assert.assertTrue(pair.first());
+        Assert.assertFalse(pair.first());
 
         // When there is SAMLRequest in params but username is null
         pair = authenticator.authenticate(null, "random", 1l, params);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/107595a6/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
new file mode 100644
index 0000000..bd87831
--- /dev/null
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
@@ -0,0 +1,74 @@
+//
+// 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;
+
+import junit.framework.TestCase;
+import org.apache.cloudstack.saml.SAMLUtils;
+import org.junit.Test;
+import org.opensaml.saml2.core.AuthnRequest;
+import org.opensaml.saml2.core.LogoutRequest;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+public class SAMLUtilsTest extends TestCase {
+
+    @Test
+    public void testGenerateSecureRandomId() throws Exception {
+        assertTrue(SAMLUtils.generateSecureRandomId().length() > 0);
+    }
+
+    @Test
+    public void testBuildAuthnRequestObject() throws Exception {
+        String consumerUrl = "http://someurl.com";
+        String idpUrl = "http://idp.domain.example";
+        String spId = "cloudstack";
+        String authnId = SAMLUtils.generateSecureRandomId();
+        AuthnRequest req = SAMLUtils.buildAuthnRequestObject(authnId, spId, idpUrl, consumerUrl);
+        assertEquals(req.getAssertionConsumerServiceURL(), consumerUrl);
+        assertEquals(req.getDestination(), idpUrl);
+        assertEquals(req.getIssuer().getValue(), spId);
+    }
+
+    @Test
+    public void testBuildLogoutRequest() throws Exception {
+        String logoutUrl = "http://logoutUrl";
+        String spId = "cloudstack";
+        String nameId = "_12345";
+        LogoutRequest req = SAMLUtils.buildLogoutRequest(logoutUrl, spId, nameId);
+        assertEquals(req.getDestination(), logoutUrl);
+        assertEquals(req.getIssuer().getValue(), spId);
+    }
+
+    @Test
+    public void testX509Helpers() throws Exception {
+        KeyPair keyPair = SAMLUtils.generateRandomKeyPair();
+
+        String privateKeyString = SAMLUtils.savePrivateKey(keyPair.getPrivate());
+        String publicKeyString = SAMLUtils.savePublicKey(keyPair.getPublic());
+
+        PrivateKey privateKey = SAMLUtils.loadPrivateKey(privateKeyString);
+        PublicKey publicKey = SAMLUtils.loadPublicKey(publicKeyString);
+
+        assertTrue(privateKey.equals(keyPair.getPrivate()));
+        assertTrue(publicKey.equals(keyPair.getPublic()));
+    }
+}
\ No newline at end of file


Mime
View raw message