cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bhais...@apache.org
Subject [4/4] git commit: updated refs/heads/saml-pp-squashed to faa0245
Date Mon, 22 Jun 2015 10:50:32 GMT
CLOUDSTACK-8457: SAML auth plugin improvements for production usage

* Move config options to SAML plugin
  This moves all configuration options from Config.java to SAML auth manager. This
  allows us to use the config framework.
* Make SAML2UserAuthenticator validate SAML token in httprequest
* Make logout API use ConfigKeys defined in saml auth manager
* Before doing SAML auth, cleanup local states and cookies
* Fix configurations in 4.5.1 to 4.5.2 upgrade path
* Fail if idp has no sso URL defined
* Add a default set of SAML SP cert for testing purposes
  Now to enable and use saml, one needs to do a deploydb-saml after doing a deploydb

- CLOUDSTACK-8458:
    * On UI show dropdown list of discovered IdPs
    * 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

- CLOUDSTACK-8459:
    * Add HTTP-POST binding to SP metadata
    * Authn requests must use either HTTP POST/Artifact binding

- CLOUDSTACK-8461:
    * Use unspecified x509 cert as a fallback encryption/signing key
      In case a IDP's metadata does not clearly say if their certificates need to be
      used as signing or encryption and we don't find that, fallback to use the
      unspecified key itself.

- CLOUDSTACK-8462:
    * SAML Auth plugin should not do authorization
      This removes logic to create user if they don't exist. This strictly now
      assumes that users have been already created/imported/authorized by admins.
      As per SAML v2.0 spec section 4.1.2, the SP provider should create authn requests using
      either HTTP POST or HTTP Artifact binding to transfer the message through a
      user agent (browser in our case). The use of HTTP Redirect was one of the reasons
      why this plugin failed to work for some IdP servers that enforce this.
    * Add new User Source
      By reusing the source field, we can find if a user has been SAML enabled or not.
      The limitation is that, once say a user is imported by LDAP and then SAML
      enabled - they won't be able to use LDAP for authentication
    * UI should allow users to pass in domain they want to log into
    * SAML users need to be authorized before they can authenticate
        - New column entity to track saml entity id for a user
        - Reusing source column to check if user is saml enabled or not
        - Add new source types, saml2 and saml2disabled
        - New table saml_token to solve the issue of multiple users across domains and
          to enforce security by tracking authn token and checking the samlresponse for
          the tokens
        - Implement API: authorizeSamlSso to enable/disable saml authentication for a
          user
        - Stubs to implement saml token flushing/expiry

- CLOUDSTACK-8463:
    * Use username attribute specified in global setting
      Use username attribute defined by admin from a global setting
      In case of encrypted assertion/attributes:
      - Decrypt them
      - Check signature if provided to check authenticity of message using IdP's
        public key and SP's private key
      - Loop through attributes to find the username

- CLOUDSTACK-8538:
    * Add new global config for SAML request sig algorithm

- CLOUDSTACK-8539:
    * Add metadata refresh timer task and token expiring
        - Fix domain path and save it to saml_tokens
        - Expire hour old saml tokens
        - Refresh metadata based on timer task
        - Fix unit tests

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


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

Branch: refs/heads/saml-pp-squashed
Commit: faa02452b89662e4239a760a5e9dd37040a88d17
Parents: eb904cd
Author: Rohit Yadav <rohit.yadav@shapeblue.com>
Authored: Thu May 28 14:50:12 2015 +0200
Committer: Rohit Yadav <rohit.yadav@shapeblue.com>
Committed: Mon Jun 22 12:49:56 2015 +0200

----------------------------------------------------------------------
 api/src/com/cloud/user/User.java                |   7 +-
 api/src/com/cloud/user/UserAccount.java         |   4 +
 .../org/apache/cloudstack/api/ApiConstants.java |   3 +-
 .../classes/resources/messages.properties       |   7 +-
 .../classes/resources/messages_fr_FR.properties |   2 +-
 .../classes/resources/messages_hu.properties    |   2 +-
 .../classes/resources/messages_ja_JP.properties |   2 +-
 .../classes/resources/messages_nb_NO.properties |   2 +-
 .../classes/resources/messages_nl_NL.properties |   2 +-
 .../classes/resources/messages_pt_BR.properties |   2 +-
 .../classes/resources/messages_zh_CN.properties |   2 +-
 client/tomcatconf/commands.properties.in        |   2 +
 developer/developer-prefill.sql                 |   5 -
 developer/developer-saml.sql                    |  63 +++
 developer/pom.xml                               |  58 +++
 .../com/cloud/upgrade/dao/Upgrade451to452.java  |  19 +-
 .../src/com/cloud/user/UserAccountVO.java       |  11 +
 engine/schema/src/com/cloud/user/UserVO.java    |  10 +
 .../src/com/cloud/user/dao/UserAccountDao.java  |   4 +
 .../com/cloud/user/dao/UserAccountDaoImpl.java  |  18 +-
 plugins/user-authenticators/saml2/pom.xml       |   5 +
 .../cloudstack/saml2/spring-saml2-context.xml   |   3 +
 .../api/command/AuthorizeSAMLSSOCmd.java        |  79 ++++
 .../command/GetServiceProviderMetaDataCmd.java  |  81 +++-
 .../cloudstack/api/command/ListIdpsCmd.java     | 114 +++++
 .../command/SAML2LoginAPIAuthenticatorCmd.java  | 265 ++++++------
 .../command/SAML2LogoutAPIAuthenticatorCmd.java |  28 +-
 .../cloudstack/api/response/IdpResponse.java    |  62 +++
 .../cloudstack/saml/SAML2AuthManager.java       |  70 +++-
 .../cloudstack/saml/SAML2AuthManagerImpl.java   | 419 +++++++++++++++----
 .../cloudstack/saml/SAML2UserAuthenticator.java |  28 +-
 .../cloudstack/saml/SAMLPluginConstants.java    |  29 ++
 .../cloudstack/saml/SAMLProviderMetadata.java   | 122 ++++++
 .../apache/cloudstack/saml/SAMLTokenDao.java    |  23 +
 .../cloudstack/saml/SAMLTokenDaoImpl.java       |  51 +++
 .../org/apache/cloudstack/saml/SAMLTokenVO.java |  97 +++++
 .../org/apache/cloudstack/saml/SAMLUtils.java   | 351 ++++++++++++++++
 .../GetServiceProviderMetaDataCmdTest.java      | 102 +++++
 .../cloudstack/SAML2UserAuthenticatorTest.java  |   8 +-
 .../org/apache/cloudstack/SAMLUtilsTest.java    |  74 ++++
 .../GetServiceProviderMetaDataCmdTest.java      |  98 -----
 .../SAML2LoginAPIAuthenticatorCmdTest.java      |  45 +-
 .../SAML2LogoutAPIAuthenticatorCmdTest.java     |  15 +-
 server/src/com/cloud/api/ApiServer.java         |   4 +-
 server/src/com/cloud/api/ApiServlet.java        |   2 +-
 server/src/com/cloud/configuration/Config.java  |  72 ----
 setup/db/db/schema-451to452-cleanup.sql         |  20 +
 setup/db/db/schema-451to452.sql                 |  35 ++
 tools/apidoc/gen_toc.py                         |   2 +
 ui/css/cloudstack3.css                          |  34 +-
 ui/dictionary.jsp                               |   3 +
 ui/index.jsp                                    |  49 ++-
 ui/scripts/accountsWizard.js                    |  63 ++-
 ui/scripts/cloudStack.js                        |  22 +-
 ui/scripts/docs.js                              |   8 +
 ui/scripts/sharedFunctions.js                   |   1 +
 ui/scripts/ui-custom/accountsWizard.js          |   5 +
 ui/scripts/ui-custom/login.js                   |  52 ++-
 .../apache/cloudstack/utils/auth/SAMLUtils.java | 330 ---------------
 .../cloudstack/utils/auth/SAMLUtilsTest.java    |  91 ----
 60 files changed, 2237 insertions(+), 950 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/api/src/com/cloud/user/User.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/user/User.java b/api/src/com/cloud/user/User.java
index 33d6235..1f0dcfd 100644
--- a/api/src/com/cloud/user/User.java
+++ b/api/src/com/cloud/user/User.java
@@ -23,7 +23,7 @@ import org.apache.cloudstack.api.InternalIdentity;
 public interface User extends OwnedBy, InternalIdentity {
 
     public enum Source {
-        LDAP, UNKNOWN
+        LDAP, SAML2, SAML2DISABLED, UNKNOWN
     }
 
     public static final long UID_SYSTEM = 1;
@@ -83,4 +83,9 @@ public interface User extends OwnedBy, InternalIdentity {
 
     public Source getSource();
 
+    void setSource(Source source);
+
+    public String getExternalEntity();
+
+    public void setExternalEntity(String entity);
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/api/src/com/cloud/user/UserAccount.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/user/UserAccount.java b/api/src/com/cloud/user/UserAccount.java
index d44fcf7..0449514 100644
--- a/api/src/com/cloud/user/UserAccount.java
+++ b/api/src/com/cloud/user/UserAccount.java
@@ -63,4 +63,8 @@ public interface UserAccount extends InternalIdentity {
     int getLoginAttempts();
 
     public User.Source getSource();
+
+    public String getExternalEntity();
+
+    public void setExternalEntity(String entity);
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/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..2471e08 100755
--- a/api/src/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/org/apache/cloudstack/api/ApiConstants.java
@@ -373,6 +373,7 @@ public class ApiConstants {
     public static final String ISOLATION_METHODS = "isolationmethods";
     public static final String PHYSICAL_NETWORK_ID = "physicalnetworkid";
     public static final String DEST_PHYSICAL_NETWORK_ID = "destinationphysicalnetworkid";
+    public static final String ENABLE = "enable";
     public static final String ENABLED = "enabled";
     public static final String SERVICE_NAME = "servicename";
     public static final String DHCP_RANGE = "dhcprange";
@@ -515,7 +516,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/faa02452/client/WEB-INF/classes/resources/messages.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties
index 523c7cc..1d40037 100644
--- a/client/WEB-INF/classes/resources/messages.properties
+++ b/client/WEB-INF/classes/resources/messages.properties
@@ -71,7 +71,7 @@ message.action.delete.nic=Please confirm that want to remove this NIC, which wil
 changed.item.properties=Changed item properties
 confirm.enable.s3=Please fill in the following information to enable support for S3-backed Secondary Storage
 confirm.enable.swift=Please fill in the following information to enable support for Swift
-error.could.not.change.your.password.because.ldap.is.enabled=Error could not change your password because LDAP is enabled.
+error.could.not.change.your.password.because.ldap.is.enabled=Error could not change your password because LDAP/SAML is enabled.
 error.could.not.enable.zone=Could not enable zone
 error.installWizard.message=Something went wrong; you may go back and correct any errors
 error.invalid.username.password=Invalid username or password
@@ -757,7 +757,10 @@ label.local.storage=Local Storage
 label.local=Local
 label.login=Login
 label.logout=Logout
-label.saml.login=SAML Login
+label.saml.login=SAML Single Sign On
+label.saml.enable=Enable SAML SSO
+label.saml.entity=Identity Provider
+label.add.LDAP.account=Add LDAP Account
 label.LUN.number=LUN \#
 label.lun=LUN
 label.make.project.owner=Make account project owner

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/client/WEB-INF/classes/resources/messages_fr_FR.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages_fr_FR.properties b/client/WEB-INF/classes/resources/messages_fr_FR.properties
index eddc664..4f4828d 100644
--- a/client/WEB-INF/classes/resources/messages_fr_FR.properties
+++ b/client/WEB-INF/classes/resources/messages_fr_FR.properties
@@ -18,7 +18,7 @@
 changed.item.properties=Propri\u00e9t\u00e9s de l\\'\u00e9l\u00e9ment modifi\u00e9es
 confirm.enable.s3=Remplir les informations suivantes pour activer le support de stockage secondaire S3
 confirm.enable.swift=Remplir les informations suivantes pour activer Swift
-error.could.not.change.your.password.because.ldap.is.enabled=Erreur\: impossible de changer votre mot de passe car le mode LDAP est activ\u00e9.
+error.could.not.change.your.password.because.ldap.is.enabled=Erreur\: impossible de changer votre mot de passe car le mode LDAP/SAML est activ\u00e9.
 error.could.not.enable.zone=Impossible d\\'activer la zone
 error.installWizard.message=Une erreur s\\'est produite ; vous pouvez retourner en arri\u00e8re et corriger les erreurs
 error.invalid.username.password=Identifiant ou mot de passe invalide

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/client/WEB-INF/classes/resources/messages_hu.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages_hu.properties b/client/WEB-INF/classes/resources/messages_hu.properties
index 9ae73e8..22b70d4 100644
--- a/client/WEB-INF/classes/resources/messages_hu.properties
+++ b/client/WEB-INF/classes/resources/messages_hu.properties
@@ -18,7 +18,7 @@
 changed.item.properties=Az elem tulajdons\u00e1gai megv\u00e1ltoztak
 confirm.enable.s3=T\u00f6ltsd ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat az S3 m\u00e1sodlagos t\u00e1r bekapcsol\u00e1s\u00e1hoz\!
 confirm.enable.swift=T\u00f6ltsd ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat a Swift t\u00e1mogat\u00e1s bekapcsol\u00e1s\u00e1hoz\!
-error.could.not.change.your.password.because.ldap.is.enabled=Nem siker\u00fclt megv\u00e1ltoztatni a jelszavadat, mert az LDAP be van kapcsolva.
+error.could.not.change.your.password.because.ldap.is.enabled=Nem siker\u00fclt megv\u00e1ltoztatni a jelszavadat, mert az LDAP/SAML be van kapcsolva.
 error.could.not.enable.zone=A z\u00f3na enged\u00e9lyez\u00e9se sikertelen
 error.installWizard.message=Valami nem siker\u00fclt, visszamehetsz kijav\u00edtani a hib\u00e1kat.
 error.invalid.username.password=\u00c9rv\u00e9nytelen felhaszn\u00e1l\u00f3n\u00e9v vagy jelsz\u00f3

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/client/WEB-INF/classes/resources/messages_ja_JP.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages_ja_JP.properties b/client/WEB-INF/classes/resources/messages_ja_JP.properties
index d662ea4..6fdebb3 100644
--- a/client/WEB-INF/classes/resources/messages_ja_JP.properties
+++ b/client/WEB-INF/classes/resources/messages_ja_JP.properties
@@ -18,7 +18,7 @@
 changed.item.properties=\u9805\u76ee\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u5909\u66f4
 confirm.enable.s3=S3 \u30d9\u30fc\u30b9\u306e\u30bb\u30ab\u30f3\u30c0\u30ea \u30b9\u30c8\u30ec\u30fc\u30b8\u306e\u30b5\u30dd\u30fc\u30c8\u3092\u6709\u52b9\u306b\u3059\u308b\u306b\u306f\u3001\u6b21\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 confirm.enable.swift=Swift \u306e\u30b5\u30dd\u30fc\u30c8\u3092\u6709\u52b9\u306b\u3059\u308b\u306b\u306f\u3001\u6b21\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002
-error.could.not.change.your.password.because.ldap.is.enabled=\u30a8\u30e9\u30fc\u3002LDAP \u304c\u6709\u52b9\u306a\u305f\u3081\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002
+error.could.not.change.your.password.because.ldap.is.enabled=\u30a8\u30e9\u30fc\u3002LDAP/SAML \u304c\u6709\u52b9\u306a\u305f\u3081\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002
 error.could.not.enable.zone=\u30be\u30fc\u30f3\u3092\u6709\u52b9\u306b\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
 error.installWizard.message=\u554f\u984c\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u623b\u3063\u3066\u30a8\u30e9\u30fc\u3092\u4fee\u6b63\u3067\u304d\u307e\u3059\u3002
 error.invalid.username.password=\u7121\u52b9\u306a\u30e6\u30fc\u30b6\u30fc\u540d\u307e\u305f\u306f\u30d1\u30b9\u30ef\u30fc\u30c9\u3067\u3059\u3002

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/client/WEB-INF/classes/resources/messages_nb_NO.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages_nb_NO.properties b/client/WEB-INF/classes/resources/messages_nb_NO.properties
index 3bbf789..cdfc98a 100644
--- a/client/WEB-INF/classes/resources/messages_nb_NO.properties
+++ b/client/WEB-INF/classes/resources/messages_nb_NO.properties
@@ -17,7 +17,7 @@
 
 changed.item.properties=Endrede egenskaper
 confirm.enable.swift=Vennligst fyll inn f\u00f8lgende informasjon for \u00e5 aktivere st\u00f8tte for Swift
-error.could.not.change.your.password.because.ldap.is.enabled=Feil kunne ikke bytte ditt passord fordi LDAP er aktivert.
+error.could.not.change.your.password.because.ldap.is.enabled=Feil kunne ikke bytte ditt passord fordi LDAP/SAML er aktivert.
 error.could.not.enable.zone=Kunne ikke aktivere sonen
 error.installWizard.message=Noe gikk galt. G\u00e5 tilbake og korriger feilene.
 error.invalid.username.password=Ugyldig brukernavn eller passord

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/client/WEB-INF/classes/resources/messages_nl_NL.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages_nl_NL.properties b/client/WEB-INF/classes/resources/messages_nl_NL.properties
index 58ee3b1..1e98da9 100644
--- a/client/WEB-INF/classes/resources/messages_nl_NL.properties
+++ b/client/WEB-INF/classes/resources/messages_nl_NL.properties
@@ -18,7 +18,7 @@
 changed.item.properties=Item eigenschappen gewijzigd
 confirm.enable.s3=Vul de volgende informatie in om ondersteuning voor S3-aangestuurde Secundaire Opslag te activeren
 confirm.enable.swift=Vul de volgende informatie in om ondersteuning voor Swift te activeren
-error.could.not.change.your.password.because.ldap.is.enabled=Fout. Kan wachtwoord niet wijzigen omdat LDAP is uitgeschakeld.
+error.could.not.change.your.password.because.ldap.is.enabled=Fout. Kan wachtwoord niet wijzigen omdat LDAP/SAML is uitgeschakeld.
 error.could.not.enable.zone=Kon zone niet activeren
 error.installWizard.message=Er ging iets mis; je kunt teruggaan om de eventuele fouten te herstellen
 error.invalid.username.password=Ongeldige gebruikersnaam of wachtwoord

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/client/WEB-INF/classes/resources/messages_pt_BR.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages_pt_BR.properties b/client/WEB-INF/classes/resources/messages_pt_BR.properties
index 5da7e39..debfaf2 100644
--- a/client/WEB-INF/classes/resources/messages_pt_BR.properties
+++ b/client/WEB-INF/classes/resources/messages_pt_BR.properties
@@ -18,7 +18,7 @@
 changed.item.properties=Propriedades do item alteradas
 confirm.enable.s3=Por favor, preencha as informa\u00e7\u00f5es abaixo para habilitar suporte o Storage Secund\u00e1rio fornecido por S3
 confirm.enable.swift=Por favor, preencha as informa\u00e7\u00f5es abaixo para habilitar suporte ao Swift
-error.could.not.change.your.password.because.ldap.is.enabled=Erro\: a nuvem n\u00e3o alterou sua senha porque o LDAP est\u00e1 ativo.
+error.could.not.change.your.password.because.ldap.is.enabled=Erro\: a nuvem n\u00e3o alterou sua senha porque o LDAP/SAML est\u00e1 ativo.
 error.could.not.enable.zone=N\u00e3o foi poss\u00edvel habilitar a zona
 error.installWizard.message=Alguma coisa est\u00e1 errada; voc\u00ea pode voltar e corrigir quaisquer erros
 error.invalid.username.password=Usu\u00e1rio ou senha inv\u00e1lidos

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/client/WEB-INF/classes/resources/messages_zh_CN.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages_zh_CN.properties b/client/WEB-INF/classes/resources/messages_zh_CN.properties
index a4af8f7..7119ee7 100644
--- a/client/WEB-INF/classes/resources/messages_zh_CN.properties
+++ b/client/WEB-INF/classes/resources/messages_zh_CN.properties
@@ -18,7 +18,7 @@
 changed.item.properties=\u66f4\u6539\u9879\u76ee\u5c5e\u6027
 confirm.enable.s3=\u8bf7\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\u4ee5\u542f\u7528\u5bf9 S3 \u652f\u6301\u7684\u4e8c\u7ea7\u5b58\u50a8\u7684\u652f\u6301
 confirm.enable.swift=\u8bf7\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\u4ee5\u542f\u7528\u5bf9 SWIFT \u7684\u652f\u6301
-error.could.not.change.your.password.because.ldap.is.enabled=\u9519\u8bef\u3002LDAP \u5904\u4e8e\u542f\u7528\u72b6\u6001\uff0c\u65e0\u6cd5\u66f4\u6539\u60a8\u7684\u5bc6\u7801\u3002
+error.could.not.change.your.password.because.ldap.is.enabled=\u9519\u8bef\u3002LDAP/SAML \u5904\u4e8e\u542f\u7528\u72b6\u6001\uff0c\u65e0\u6cd5\u66f4\u6539\u60a8\u7684\u5bc6\u7801\u3002
 error.could.not.enable.zone=\u65e0\u6cd5\u542f\u7528\u8d44\u6e90\u57df
 error.installWizard.message=\u51fa\u73b0\u95ee\u9898\uff1b\u8bf7\u8fd4\u56de\u5e76\u66f4\u6b63\u4efb\u4f55\u9519\u8bef
 error.invalid.username.password=\u7528\u6237\u540d\u6216\u5bc6\u7801\u65e0\u6548

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/client/tomcatconf/commands.properties.in
----------------------------------------------------------------------
diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in
index a87d167..094cd76 100644
--- a/client/tomcatconf/commands.properties.in
+++ b/client/tomcatconf/commands.properties.in
@@ -26,6 +26,8 @@ logout=15
 samlSso=15
 samlSlo=15
 getSPMetadata=15
+listIdps=15
+authorizeSamlSso=1
 
 ### Account commands
 createAccount=7

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/developer/developer-prefill.sql
----------------------------------------------------------------------
diff --git a/developer/developer-prefill.sql b/developer/developer-prefill.sql
index 27b36e7..3097203 100644
--- a/developer/developer-prefill.sql
+++ b/developer/developer-prefill.sql
@@ -83,9 +83,4 @@ INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
             VALUES ('Advanced', 'DEFAULT', 'management-server',
             'developer', 'true');
 
--- Enable SAML plugin for developers by default
-INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
-            VALUES ('Advanced', 'DEFAULT', 'management-server',
-            'saml2.enabled', 'true');
-
 commit;

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/developer/developer-saml.sql
----------------------------------------------------------------------
diff --git a/developer/developer-saml.sql b/developer/developer-saml.sql
new file mode 100644
index 0000000..18afb288
--- /dev/null
+++ b/developer/developer-saml.sql
@@ -0,0 +1,63 @@
+-- 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.
+
+-- SAML keystore for testing, allows testing on ssocirlce and other public IdPs
+-- with pre-seeded SP metadata
+USE cloud;
+
+-- Enable SAML plugin for developers by default
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
+            VALUES ('Advanced', 'DEFAULT', 'SAML2-PLUGIN',
+            'saml2.enabled', 'true')
+            ON DUPLICATE KEY UPDATE value=VALUES(value);
+
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
+            VALUES ('Advanced', 'DEFAULT', 'SAML2-PLUGIN',
+            'saml2.default.idpid', 'https://idp.bhaisaab.org/idp/shibboleth')
+            ON DUPLICATE KEY UPDATE value=VALUES(value);
+
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
+            VALUES ('Advanced', 'DEFAULT', 'SAML2-PLUGIN',
+            'saml2.idp.metadata.url', 'http://idp.bhaisaab.org/idp/shibboleth')
+            ON DUPLICATE KEY UPDATE value=VALUES(value);
+
+-- Enable LDAP source
+INSERT INTO `cloud`.`ldap_configuration` (hostname, port)
+            VALUES ('idp.bhaisaab.org', 389);
+
+-- Fix ldap configs
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
+            VALUES ('Advanced', 'DEFAULT', 'management-server',
+            'ldap.basedn', 'ou=people,dc=idp,dc=bhaisaab,dc=org')
+            ON DUPLICATE KEY UPDATE value=VALUES(value);
+
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
+            VALUES ('Advanced', 'DEFAULT', 'management-server',
+            'ldap.bind.principal', 'cn=admin,dc=idp,dc=bhaisaab,dc=org')
+            ON DUPLICATE KEY UPDATE value=VALUES(value);
+
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
+            VALUES ('Advanced', 'DEFAULT', 'management-server',
+            'ldap.bind.password', 'password')
+            ON DUPLICATE KEY UPDATE value=VALUES(value);
+
+-- Add default set of certificates for testing
+LOCK TABLES `keystore` WRITE;
+/*!40000 ALTER TABLE `keystore` DISABLE KEYS */;
+INSERT INTO `keystore` VALUES (1,'SAMLSP_KEYPAIR','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','samlsp-keypair',NULL),(2,'S
 AMLSP_X509CERT','rO0ABXNyAC1qYXZhLnNlY3VyaXR5LmNlcnQuQ2VydGlmaWNhdGUkQ2VydGlmaWNhdGVSZXCJJ2qdya48DAIAAlsABGRhdGF0AAJbQkwABHR5cGV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHVyAAJbQqzzF/gGCFTgAgAAeHAAAASzMIIErzCCApcCBgFNmkdlAzANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDExBBcGFjaGVDbG91ZFN0YWNrMB4XDTE1MDUyNzExMjc1OVoXDTE4MDUyODExMjc1OVowGzEZMBcGA1UEAxMQQXBhY2hlQ2xvdWRTdGFjazCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANAOKu0BqNOtJxcJSBeuw4GBD77L4hRzLNfBQYRgGUz2QEKZx2Vu3nO5hDyk6+g5DPoIBWpKNg1uduDWmHftl+/+3MZSDZXlykHkF8TK3YpjSYEU1vBrQsqcIBiUGVqubhdkX6hnSZofpx1ugd0eISo1GxzKWddgDbMcpTdigR85H71pyXjPng8DK4+Ov1JZQ9MDJh/fbqLSr6HcnidPoXAEUr7IpILU95Ub++JhDSi1TVRQORDelfzWp8l83/oiHtbi/vcuBSVmxMEzOW7+QnMy4/lZzkiRToy37wQcxl4uc3RkSuQyTQMJATXiEbxbtLz+KdQmnAmWZvkTrfhmry7QjXNVSZKYAaq++ookEaepjlf+PZuwDwHaoWytzFqwiWQppYu9j4C9cPlfQ4fAhnNFu64K2C5LPrvULsLCiTxHc3570BMMeYb2ALXQ0loHp1QED2nZR0DzZNHvfHqcZ7yksmcbJ5wRB/HPX6m09UAQ2PVGXdavC791s939fqKeVim7CWydaLctiuSC5P/1bdna66rk9NVPTjNzJ6/7nSBWcM/NOs0Jl8emYw1AipkYWYUrPib5VRmbNk52DVXHnW3363uI
 cYeoWTr++eqpaLIS5MQUBoL2SubVU3Ma+ejkvI2bkOT0kYAcV+OA1X76+IZOlI8t4R4yeW8+5AnLk4kzAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAHZWSGpypDmQLQWr2FCVQUnulbPuMMJ0sCH0rNLGLe8qNbZ0YeAuWFsg7+0kVGZ4OuDgioIhD0h3Q3huZtF/WF81eyZqPyVfkXG8egjK58AzMDPHZECeoSVGUCZuq3wjmbnT2sLLDvr8RrzMbbCEvkrYHWivQ18Lbd3eWYYnDbXZRy9GuSWrA9cMqXVYjSTxam9Kel33BIF6CAlMQN5o11oiAv+ciNoxHqGh+8xX3kFKP+x+SRt40NOEs537lEpj/6KdLvd/bP6J4K94jAX3lsdg6zDaBiQWl7P3t50AKtP384Qsb/33uXcbTyw/TkzvPcbmsgTbEUTZIOv44CxMstFrUCyT7ptrzLvDk7Iy2cMgWghULgDvKT3esPE9pleyHG8bkjGt9ypDF/Lmp7j/kILYbF7eq1wIbHOSam4p8WyddVsW4nesu6fqLiCGXum9paChIfvL3To/VHFFKduhJd0Y7LMgWO7pXxWh7XfgRmzQaEN1eJmj5315HEYTS2wXWjptwYDrhiobKuCbpADfOQks8xNKJFLMnXp+IvAqz+ZjkNOz60MLuQ3hvKLTo6nQcTYTfZZxo3Aap30/hA2GtxxSXK/xpBDm58jcVoudgCdxML/OqERBfcADBLvIw5h9+DlXjPUg25IefU0oA336YtnzftJ6cfQfatrc0tBqNEeXdAAFWC41MDk=','','samlsp-x509cert',NULL);
+/*!40000 ALTER TABLE `keystore` ENABLE KEYS */;
+UNLOCK TABLES;

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/developer/pom.xml
----------------------------------------------------------------------
diff --git a/developer/pom.xml b/developer/pom.xml
index e39820b..6717a62 100644
--- a/developer/pom.xml
+++ b/developer/pom.xml
@@ -173,6 +173,64 @@
       </build>
     </profile>
     <profile>
+      <!-- saml deploydb property -->
+      <id>deploydb-saml</id>
+      <activation>
+        <property>
+          <name>deploydb-saml</name>
+        </property>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>exec-maven-plugin</artifactId>
+            <dependencies>
+              <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>${cs.mysql.version}</version>
+              </dependency>
+            </dependencies>
+            <version>1.2.1</version>
+            <executions>
+              <execution>
+                <phase>process-resources</phase>
+                <id>create-schema-simulator</id>
+                <goals>
+                  <goal>java</goal>
+                </goals>
+              </execution>
+            </executions>
+            <configuration>
+              <mainClass>com.cloud.upgrade.DatabaseCreator</mainClass>
+              <includePluginDependencies>true</includePluginDependencies>
+              <arguments>
+                <!-- db properties file -->
+                <argument>${basedir}/../utils/conf/db.properties</argument>
+                <argument>${basedir}/../utils/conf/db.properties.override</argument>
+                <!-- simulator sql files -->
+                <argument>${basedir}/developer-saml.sql</argument>
+                <!-- upgrade -->
+                <argument>com.cloud.upgrade.DatabaseUpgradeChecker</argument>
+                <argument>--rootpassword=${db.root.password}</argument>
+              </arguments>
+              <systemProperties>
+                <systemProperty>
+                  <key>catalina.home</key>
+                  <value>${basedir}/../utils</value>
+                </systemProperty>
+                <systemProperty>
+                  <key>paths.script</key>
+                  <value>${basedir}/target/db</value>
+                </systemProperty>
+              </systemProperties>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+    <profile>
       <!-- simulator deploydb property -->
       <id>deploydb-simulator</id>
       <activation>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/engine/schema/src/com/cloud/upgrade/dao/Upgrade451to452.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/upgrade/dao/Upgrade451to452.java b/engine/schema/src/com/cloud/upgrade/dao/Upgrade451to452.java
index 3b7b643..870e534 100644
--- a/engine/schema/src/com/cloud/upgrade/dao/Upgrade451to452.java
+++ b/engine/schema/src/com/cloud/upgrade/dao/Upgrade451to452.java
@@ -17,11 +17,13 @@
 
 package com.cloud.upgrade.dao;
 
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.Script;
+import org.apache.log4j.Logger;
+
 import java.io.File;
 import java.sql.Connection;
 
-import org.apache.log4j.Logger;
-
 public class Upgrade451to452 implements DbUpgrade {
     final static Logger s_logger = Logger.getLogger(Upgrade451to452.class);
 
@@ -42,7 +44,11 @@ public class Upgrade451to452 implements DbUpgrade {
 
     @Override
     public File[] getPrepareScripts() {
-        return new File[] {};
+        String script = Script.findScript("", "db/schema-451to452.sql");
+        if (script == null) {
+            throw new CloudRuntimeException("Unable to find db/schema-451to452.sql");
+        }
+        return new File[] {new File(script)};
     }
 
     @Override
@@ -51,6 +57,11 @@ public class Upgrade451to452 implements DbUpgrade {
 
     @Override
     public File[] getCleanupScripts() {
-        return null;
+        String script = Script.findScript("", "db/schema-451to452-cleanup.sql");
+        if (script == null) {
+            throw new CloudRuntimeException("Unable to find db/schema-451to452-cleanup.sql");
+        }
+
+        return new File[] {new File(script)};
     }
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/engine/schema/src/com/cloud/user/UserAccountVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/user/UserAccountVO.java b/engine/schema/src/com/cloud/user/UserAccountVO.java
index 5f33c47..80ee873 100644
--- a/engine/schema/src/com/cloud/user/UserAccountVO.java
+++ b/engine/schema/src/com/cloud/user/UserAccountVO.java
@@ -105,6 +105,9 @@ public class UserAccountVO implements UserAccount, InternalIdentity {
     @Enumerated(value = EnumType.STRING)
     private User.Source source;
 
+    @Column(name = "external_entity", length = 65535)
+    private String externalEntity = null;
+
     public UserAccountVO() {
     }
 
@@ -296,4 +299,12 @@ public class UserAccountVO implements UserAccount, InternalIdentity {
     public void setSource(User.Source source) {
         this.source = source;
     }
+
+    public String getExternalEntity() {
+        return externalEntity;
+    }
+
+    public void setExternalEntity(String externalEntity) {
+        this.externalEntity = externalEntity;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/engine/schema/src/com/cloud/user/UserVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/user/UserVO.java b/engine/schema/src/com/cloud/user/UserVO.java
index eb2813b..da7811e 100644
--- a/engine/schema/src/com/cloud/user/UserVO.java
+++ b/engine/schema/src/com/cloud/user/UserVO.java
@@ -101,6 +101,9 @@ public class UserVO implements User, Identity, InternalIdentity {
     @Enumerated(value = EnumType.STRING)
     private Source source;
 
+    @Column(name = "external_entity", length = 65535)
+    private String externalEntity;
+
     public UserVO() {
         this.uuid = UUID.randomUUID().toString();
     }
@@ -283,4 +286,11 @@ public class UserVO implements User, Identity, InternalIdentity {
         this.source = source;
     }
 
+    public String getExternalEntity() {
+        return externalEntity;
+    }
+
+    public void setExternalEntity(String externalEntity) {
+        this.externalEntity = externalEntity;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/engine/schema/src/com/cloud/user/dao/UserAccountDao.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/user/dao/UserAccountDao.java b/engine/schema/src/com/cloud/user/dao/UserAccountDao.java
index a26ff7f..a7779b0 100644
--- a/engine/schema/src/com/cloud/user/dao/UserAccountDao.java
+++ b/engine/schema/src/com/cloud/user/dao/UserAccountDao.java
@@ -20,7 +20,11 @@ import com.cloud.user.UserAccount;
 import com.cloud.user.UserAccountVO;
 import com.cloud.utils.db.GenericDao;
 
+import java.util.List;
+
 public interface UserAccountDao extends GenericDao<UserAccountVO, Long> {
+    List<UserAccountVO> getAllUsersByName(String username);
+
     UserAccount getUserAccount(String username, Long domainId);
 
     boolean validateUsernameInDomain(String username, Long domainId);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/engine/schema/src/com/cloud/user/dao/UserAccountDaoImpl.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/user/dao/UserAccountDaoImpl.java b/engine/schema/src/com/cloud/user/dao/UserAccountDaoImpl.java
index 1449e6b..4feb7cd 100644
--- a/engine/schema/src/com/cloud/user/dao/UserAccountDaoImpl.java
+++ b/engine/schema/src/com/cloud/user/dao/UserAccountDaoImpl.java
@@ -16,15 +16,15 @@
 // under the License.
 package com.cloud.user.dao;
 
-import javax.ejb.Local;
-
-import org.springframework.stereotype.Component;
-
 import com.cloud.user.UserAccount;
 import com.cloud.user.UserAccountVO;
 import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
+import org.springframework.stereotype.Component;
+
+import javax.ejb.Local;
+import java.util.List;
 
 @Component
 @Local(value = {UserAccountDao.class})
@@ -39,6 +39,16 @@ public class UserAccountDaoImpl extends GenericDaoBase<UserAccountVO, Long> impl
     }
 
     @Override
+    public List<UserAccountVO> getAllUsersByName(String username) {
+        if (username == null) {
+            return null;
+        }
+        SearchCriteria<UserAccountVO> sc = createSearchCriteria();
+        sc.addAnd("username", SearchCriteria.Op.EQ, username);
+        return listBy(sc);
+    }
+
+    @Override
     public UserAccount getUserAccount(String username, Long domainId) {
         if ((username == null) || (domainId == null)) {
             return null;

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/plugins/user-authenticators/saml2/pom.xml
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/pom.xml b/plugins/user-authenticators/saml2/pom.xml
index fed1a54..c83b190 100644
--- a/plugins/user-authenticators/saml2/pom.xml
+++ b/plugins/user-authenticators/saml2/pom.xml
@@ -47,5 +47,10 @@
       <artifactId>cloud-api</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-framework-config</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 </project>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml b/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
index 92f89b8..d3a2194 100644
--- a/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
+++ b/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
@@ -33,4 +33,7 @@
         <property name="name" value="SAML2Auth"/>
     </bean>
 
+    <bean id="samlTokenDao" class="org.apache.cloudstack.saml.SAMLTokenDaoImpl">
+    </bean>
+
 </beans>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/AuthorizeSAMLSSOCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/AuthorizeSAMLSSOCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/AuthorizeSAMLSSOCmd.java
new file mode 100644
index 0000000..9e84cb4
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/AuthorizeSAMLSSOCmd.java
@@ -0,0 +1,79 @@
+package org.apache.cloudstack.api.command;
+
+import com.cloud.user.Account;
+import com.cloud.user.User;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.IdpResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.api.response.UserResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+
+@APICommand(name = "authorizeSamlSso", description = "Allow or disallow a user to use SAML SSO", responseObject = SuccessResponse.class, requestHasSensitiveInfo = true, responseHasSensitiveInfo = true)
+public class AuthorizeSAMLSSOCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(AuthorizeSAMLSSOCmd.class.getName());
+
+    private static final String s_name = "authorizesamlssoresponse";
+
+    @Inject
+    SAML2AuthManager _samlAuthManager;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "User uuid")
+    private Long id;
+
+    @Parameter(name = ApiConstants.ENABLE, type = CommandType.BOOLEAN, required = true, description = "If true, authorizes user to be able to use SAML for Single Sign. If False, disable user to user SAML SSO.")
+    private Boolean enable;
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public String getEntityId() {
+        return entityId;
+    }
+
+    @Parameter(name = ApiConstants.ENTITY_ID, type = CommandType.STRING, entityType = IdpResponse.class, description = "The Identity Provider ID the user is allowed to get single signed on from")
+    private String entityId;
+
+    public Long getId() {
+        return id;
+    }
+
+    @Override
+    public String getCommandName() {
+        return s_name;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        User user = _entityMgr.findById(User.class, getId());
+        if (user != null) {
+            return user.getAccountId();
+        }
+
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public void execute() {
+        CallContext.current().setEventDetails("UserId: " + getId());
+        SuccessResponse response = new SuccessResponse();
+        Boolean status = false;
+        if (_samlAuthManager.authorizeUser(getId(), getEntityId(), getEnable())) {
+            status = true;
+        }
+        response.setResponseName(getCommandName());
+        response.setSuccess(status);
+        setResponseObject(response);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/faa02452/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 e730836..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());
         }
@@ -159,19 +181,50 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent
         spSSODescriptor.getNameIDFormats().add(transientNameIDFormat);
 
         AssertionConsumerService assertionConsumerService = new AssertionConsumerServiceBuilder().buildObject();
-        assertionConsumerService.setIndex(0);
-        assertionConsumerService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
-        assertionConsumerService.setLocation(_samlAuthManager.getSpSingleSignOnUrl());
+        assertionConsumerService.setIndex(1);
+        assertionConsumerService.setIsDefault(true);
+        assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
+        assertionConsumerService.setLocation(spMetadata.getSsoUrl());
+        spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService);
+
+        AssertionConsumerService assertionConsumerService2 = new AssertionConsumerServiceBuilder().buildObject();
+        assertionConsumerService2.setIndex(2);
+        assertionConsumerService2.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
+        assertionConsumerService2.setLocation(spMetadata.getSsoUrl());
+        spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService2);
 
         SingleLogoutService ssoService = new SingleLogoutServiceBuilder().buildObject();
         ssoService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
-        ssoService.setLocation(_samlAuthManager.getSpSingleLogOutUrl());
-
+        ssoService.setLocation(spMetadata.getSloUrl());
         spSSODescriptor.getSingleLogoutServices().add(ssoService);
-        spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService);
+
+        SingleLogoutService ssoService2 = new SingleLogoutServiceBuilder().buildObject();
+        ssoService2.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
+        ssoService2.setLocation(spMetadata.getSloUrl());
+        spSSODescriptor.getSingleLogoutServices().add(ssoService2);
+
         spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS);
         spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor);
 
+        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/faa02452/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..3771a95
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListIdpsCmd.java
@@ -0,0 +1,114 @@
+// 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()) {
+            if (metadata == null) {
+                continue;
+            }
+            IdpResponse idpResponse = new IdpResponse();
+            idpResponse.setId(metadata.getEntityId());
+            if (metadata.getOrganizationName() == null || metadata.getOrganizationName().isEmpty()) {
+                idpResponse.setOrgName(metadata.getEntityId());
+            } else {
+                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/faa02452/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 a10afb6..d4fa51f 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
@@ -18,8 +18,6 @@
 package org.apache.cloudstack.api.command;
 
 import com.cloud.api.response.ApiResponseSerializer;
-import com.cloud.configuration.Config;
-import com.cloud.domain.Domain;
 import com.cloud.exception.CloudAuthenticationException;
 import com.cloud.user.Account;
 import com.cloud.user.DomainManager;
@@ -38,24 +36,29 @@ 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.LoginCmdResponse;
-import org.apache.cloudstack.context.CallContext;
-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.SAMLTokenVO;
+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.Attribute;
-import org.opensaml.saml2.core.AttributeStatement;
-import org.opensaml.saml2.core.AuthnRequest;
-import org.opensaml.saml2.core.NameID;
-import org.opensaml.saml2.core.NameIDType;
+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;
 import org.opensaml.xml.ConfigurationException;
-import org.opensaml.xml.io.MarshallingException;
+import org.opensaml.xml.encryption.DecryptionException;
+import org.opensaml.xml.encryption.EncryptedKeyResolver;
+import org.opensaml.xml.encryption.InlineEncryptedKeyResolver;
 import org.opensaml.xml.io.UnmarshallingException;
+import org.opensaml.xml.security.SecurityHelper;
+import org.opensaml.xml.security.credential.Credential;
+import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
 import org.opensaml.xml.security.x509.BasicX509Credential;
+import org.opensaml.xml.signature.Signature;
 import org.opensaml.xml.signature.SignatureValidator;
 import org.opensaml.xml.validation.ValidationException;
 import org.xml.sax.SAXException;
@@ -69,12 +72,8 @@ 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;
-import java.util.UUID;
 
 @APICommand(name = "samlSso", description = "SP initiated SAML Single Sign On", requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, entityType = {})
 public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
@@ -84,16 +83,14 @@ 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;
     @Inject
     EntityManager _entityMgr;
     @Inject
-    ConfigurationDao _configDao;
-    @Inject
     DomainManager _domainMgr;
     @Inject
     private UserAccountDao _userAccountDao;
@@ -104,8 +101,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
 
-    public String getIdpUrl() {
-        return idpUrl;
+    public String getIdpId() {
+        return idpId;
     }
 
     /////////////////////////////////////////////////////
@@ -128,30 +125,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 {
@@ -167,13 +140,46 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
     @Override
     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);
-                if (idps != null && idps.length > 0) {
-                    idpUrl = idps[0];
+            if (!params.containsKey(SAMLPluginConstants.SAML_RESPONSE) && !params.containsKey("SAMLart")) {
+                String idpId = null;
+                String domainPath = null;
+
+                if (params.containsKey(ApiConstants.IDP_ID)) {
+                    idpId = ((String[])params.get(ApiConstants.IDP_ID))[0];
                 }
-                String redirectUrl = this.buildAuthnRequestUrl(idpUrl);
+
+                if (params.containsKey(ApiConstants.DOMAIN)) {
+                    domainPath = ((String[])params.get(ApiConstants.DOMAIN))[0];
+                }
+
+                if (domainPath != null && !domainPath.isEmpty()) {
+                    if (!domainPath.startsWith("/")) {
+                        domainPath = "/" + domainPath;
+                    }
+                    if (!domainPath.endsWith("/")) {
+                        domainPath = domainPath + "/";
+                    }
+                } else {
+                    domainPath = SAML2AuthManager.SAMLDefaultDomainPath.value();
+                }
+
+                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));
+                }
+                if (idpMetadata.getSsoUrl() == null || idpMetadata.getSsoUrl().isEmpty()) {
+                    throw new ServerApiException(ApiErrorCode.PARAM_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
+                            "IdP ID (" + idpId + ") has no Single Sign On URL defined please contact "
+                                    + idpMetadata.getContactPersonName() + " <" + idpMetadata.getContactPersonEmail() + ">, cannot proceed.",
+                            params, responseType));
+                }
+                String authnId = SAMLUtils.generateSecureRandomId();
+                _samlAuthManager.saveToken(authnId, domainPath, idpMetadata.getEntityId());
+                s_logger.debug("Sending SAMLRequest id=" + authnId);
+                String redirectUrl = SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value());
                 resp.sendRedirect(redirectUrl);
                 return "";
             } if (params.containsKey("SAMLart")) {
@@ -181,7 +187,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)) {
@@ -190,10 +196,37 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
                             params, responseType));
                 }
 
-                if (_samlAuthManager.getIdpSigningKey() != null) {
-                    org.opensaml.xml.signature.Signature sig = processedSAMLResponse.getSignature();
+                String username = null;
+                Long domainId = null;
+                Issuer issuer = processedSAMLResponse.getIssuer();
+                SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata();
+                SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(issuer.getValue());
+
+                String responseToId = processedSAMLResponse.getInResponseTo();
+                s_logger.debug("Received SAMLResponse in response to id=" + responseToId);
+                SAMLTokenVO token = _samlAuthManager.getToken(responseToId);
+                if (token != null) {
+                    if (token.getDomainId() != null) {
+                        domainId = token.getDomainId();
+                    }
+                    if (!(token.getEntity().equalsIgnoreCase(issuer.getValue()))) {
+                        throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                                "The SAML response contains Issuer Entity ID that is different from the original SAML request",
+                                params, responseType));
+                    }
+                } else {
+                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                            "Received SAML response for a SSO request that we may not have made or has expired, please try logging in again",
+                            params, responseType));
+                }
+
+                // Set IdpId for this session
+                session.setAttribute(SAMLPluginConstants.SAML_IDPID, issuer.getValue());
+
+                Signature sig = processedSAMLResponse.getSignature();
+                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);
@@ -204,94 +237,80 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
                                 params, responseType));
                     }
                 }
-
-                String domainString = _configDao.getValue(Config.SAMLUserDomain.key());
-
-                Long domainId = null;
-                Domain domain = _domainMgr.getDomain(domainString);
-                if (domain != null) {
-                    domainId = domain.getId();
-                } else {
-                    try {
-                        domainId = Long.parseLong(domainString);
-                    } catch (NumberFormatException ignore) {
-                    }
+                if (username == null) {
+                    username = SAMLUtils.getValueFromAssertions(processedSAMLResponse.getAssertions(), SAML2AuthManager.SAMLUserAttributeName.value());
                 }
-                if (domainId == null) {
-                    s_logger.error("The default domain ID for SAML users is not set correct, it should be a UUID. ROOT domain will be used.");
-                }
-
-                String username = null;
-                String password = SAMLUtils.generateSecureRandomId(); // Random password
-                String firstName = "";
-                String lastName = "";
-                String timeZone = "GMT";
-                String email = "";
-                short accountType = 0; // User account
-
-                Assertion assertion = processedSAMLResponse.getAssertions().get(0);
-                NameID nameId = assertion.getSubject().getNameID();
-                String sessionIndex = assertion.getAuthnStatements().get(0).getSessionIndex();
-                session.setAttribute(SAMLUtils.SAML_NAMEID, nameId);
-                session.setAttribute(SAMLUtils.SAML_SESSION, sessionIndex);
-
-                if (nameId.getFormat().equals(NameIDType.PERSISTENT) || nameId.getFormat().equals(NameIDType.EMAIL)) {
-                    username = nameId.getValue();
-                    if (nameId.getFormat().equals(NameIDType.EMAIL)) {
-                        email = username;
-                    }
-                }
-
-                List<AttributeStatement> attributeStatements = assertion.getAttributeStatements();
-                if (attributeStatements != null && attributeStatements.size() > 0) {
-                    for (AttributeStatement attributeStatement: attributeStatements) {
-                        if (attributeStatement == null) {
-                            continue;
-                        }
-                        // Try capturing standard LDAP attributes
-                        for (Attribute attribute: attributeStatement.getAttributes()) {
-                            String attributeName = attribute.getName();
-                            String attributeValue = attribute.getAttributeValues().get(0).getDOM().getTextContent();
-                            if (attributeName.equalsIgnoreCase("uid") && username == null) {
-                                username = attributeValue;
-                            } else if (attributeName.equalsIgnoreCase("givenName")) {
-                                firstName = attributeValue;
-                            } else if (attributeName.equalsIgnoreCase(("sn"))) {
-                                lastName = attributeValue;
-                            } else if (attributeName.equalsIgnoreCase("mail")) {
-                                email = attributeValue;
+                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);
+                    decrypter.setRootInNewDocument(true);
+                    List<EncryptedAssertion> encryptedAssertions = processedSAMLResponse.getEncryptedAssertions();
+                    if (encryptedAssertions != null) {
+                        for (EncryptedAssertion encryptedAssertion : encryptedAssertions) {
+                            Assertion assertion = null;
+                            try {
+                                assertion = decrypter.decrypt(encryptedAssertion);
+                            } catch (DecryptionException e) {
+                                s_logger.warn("SAML EncryptedAssertion error: " + e.toString());
+                            }
+                            if (assertion == null) {
+                                continue;
+                            }
+                            Signature encSig = assertion.getSignature();
+                            if (idpMetadata.getSigningCertificate() != null && encSig != null) {
+                                BasicX509Credential sigCredential = new BasicX509Credential();
+                                sigCredential.setEntityCertificate(idpMetadata.getSigningCertificate());
+                                SignatureValidator validator = new SignatureValidator(sigCredential);
+                                try {
+                                    validator.validate(encSig);
+                                } catch (ValidationException e) {
+                                    s_logger.error("SAML Response's signature failed to be validated by IDP signing key:" + e.getMessage());
+                                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                                            "SAML Response's signature failed to be validated by IDP signing key",
+                                            params, responseType));
+                                }
+                            }
+                            if (username == null) {
+                                username = SAMLUtils.getValueFromAttributeStatements(assertion.getAttributeStatements(), SAML2AuthManager.SAMLUserAttributeName.value());
                             }
                         }
                     }
                 }
 
-                if (username == null && email != null) {
-                    username = email;
+                if (username == null) {
+                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                            "Failed to find admin configured username attribute in the SAML Response. Please ask your administrator to check SAML user attribute name.", params, responseType));
                 }
-                final String uniqueUserId = SAMLUtils.createSAMLId(username);
 
                 UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
-                if (userAccount == null && uniqueUserId != null && username != null) {
-                    CallContext.current().setEventDetails("SAML Account/User with UserName: " + username + ", FirstName :" + password + ", LastName: " + lastName);
-                    userAccount = _accountService.createUserAccount(username, password, firstName, lastName, email, timeZone,
-                            username, (short) accountType, domainId, null, null, UUID.randomUUID().toString(), uniqueUserId);
+                if (userAccount == null || userAccount.getExternalEntity() == null ||  !userAccount.getExternalEntity().equalsIgnoreCase(issuer.getValue())) {
+                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                            "Your authenticated user is not authorized, please contact your administrator",
+                            params, responseType));
                 }
 
                 if (userAccount != null) {
                     try {
                         if (_apiServer.verifyUser(userAccount.getId())) {
-                            LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, username, userAccount.getPassword(), domainId, null, remoteAddress, params);
+                            LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, userAccount.getUsername(), userAccount.getUsername() + userAccount.getSource().toString(),
+                                    userAccount.getDomainId(), null, remoteAddress, params);
                             resp.addCookie(new Cookie("userid", URLEncoder.encode(loginResponse.getUserId(), HttpUtils.UTF_8)));
                             resp.addCookie(new Cookie("domainid", URLEncoder.encode(loginResponse.getDomainId(), HttpUtils.UTF_8)));
                             resp.addCookie(new Cookie("role", URLEncoder.encode(loginResponse.getType(), HttpUtils.UTF_8)));
                             resp.addCookie(new Cookie("username", URLEncoder.encode(loginResponse.getUsername(), HttpUtils.UTF_8)));
-                            resp.addCookie(new Cookie("sessionkey", URLEncoder.encode(loginResponse.getSessionKey(), HttpUtils.UTF_8)));
+                            resp.addCookie(new Cookie(ApiConstants.SESSIONKEY, URLEncoder.encode(loginResponse.getSessionKey(), HttpUtils.UTF_8)));
                             resp.addCookie(new Cookie("account", URLEncoder.encode(loginResponse.getAccount(), HttpUtils.UTF_8)));
-                            resp.addCookie(new Cookie("timezone", URLEncoder.encode(loginResponse.getTimeZone(), HttpUtils.UTF_8)));
+                            String timezone = loginResponse.getTimeZone();
+                            if (timezone != null) {
+                                resp.addCookie(new Cookie("timezone", URLEncoder.encode(timezone, HttpUtils.UTF_8)));
+                            }
                             resp.addCookie(new Cookie("userfullname", URLEncoder.encode(loginResponse.getFirstName() + " " + loginResponse.getLastName(), HttpUtils.UTF_8).replace("+", "%20")));
-                            resp.sendRedirect(_configDao.getValue(Config.SAMLCloudStackRedirectionUrl.key()));
+                            resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value());
                             return ApiResponseSerializer.toSerializedString(loginResponse, responseType);
-
                         }
                     } catch (final CloudAuthenticationException ignored) {
                     }
@@ -302,7 +321,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
             auditTrailSb.append(e.getMessage());
         }
         throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
-                "Unable to authenticate or retrieve user while performing SAML based SSO",
+                "Unable to authenticate user while performing SAML based SSO. Please make sure your user/account has been added, enable and authorized by the admin before you can authenticate. Please contact your administrator.",
                 params, responseType));
     }
 


Mime
View raw message