You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2015/06/26 18:11:31 UTC
[4/4] git commit: updated refs/heads/saml-pp-squashed to 7310c2d
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
* UI remembers login selections, IDP server
- 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, though it is
optional and needed only when a user has accounts across domains with same
username and authorized IDP server
* 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 <ro...@shapeblue.com>
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/7310c2d4
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/7310c2d4
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/7310c2d4
Branch: refs/heads/saml-pp-squashed
Commit: 7310c2d4a10e6c9992cc5ce9fe81842cad7b2f9b
Parents: a8959bc
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Thu May 28 14:50:12 2015 +0200
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Fri Jun 26 18:11:14 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 | 6 +-
client/tomcatconf/commands.properties.in | 3 +
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 | 19 +-
plugins/user-authenticators/saml2/pom.xml | 5 +
.../cloudstack/saml2/spring-saml2-context.xml | 3 +
.../api/command/AuthorizeSAMLSSOCmd.java | 105 +++++
.../command/GetServiceProviderMetaDataCmd.java | 82 +++-
.../cloudstack/api/command/ListIdpsCmd.java | 114 +++++
.../api/command/ListSamlAuthorizationCmd.java | 95 ++++
.../command/SAML2LoginAPIAuthenticatorCmd.java | 285 ++++++------
.../command/SAML2LogoutAPIAuthenticatorCmd.java | 29 +-
.../cloudstack/api/response/IdpResponse.java | 62 +++
.../api/response/SamlAuthorizationResponse.java | 68 +++
.../cloudstack/saml/SAML2AuthManager.java | 67 ++-
.../cloudstack/saml/SAML2AuthManagerImpl.java | 430 +++++++++++++++----
.../cloudstack/saml/SAML2UserAuthenticator.java | 28 +-
.../cloudstack/saml/SAMLPluginConstants.java | 30 ++
.../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 | 354 +++++++++++++++
.../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 | 3 +
ui/css/cloudstack3.css | 13 +-
ui/dictionary.jsp | 4 +
ui/index.jsp | 48 ++-
ui/scripts/accounts.js | 102 +++++
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 | 76 +++-
.../apache/cloudstack/utils/auth/SAMLUtils.java | 330 --------------
.../cloudstack/utils/auth/SAMLUtilsTest.java | 91 ----
56 files changed, 2561 insertions(+), 942 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/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/7310c2d4/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/7310c2d4/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/7310c2d4/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..805f960 100644
--- a/client/WEB-INF/classes/resources/messages.properties
+++ b/client/WEB-INF/classes/resources/messages.properties
@@ -112,6 +112,7 @@ label.action.attach.iso=Attach ISO
label.action.cancel.maintenance.mode.processing=Cancelling Maintenance Mode....
label.action.cancel.maintenance.mode=Cancel Maintenance Mode
label.action.change.password=Change Password
+label.action.configure.samlauthorization=Configure SAML SSO Authorization
label.action.change.service.processing=Changing Service....
label.action.change.service=Change Service
label.action.copy.ISO.processing=Copying ISO....
@@ -757,7 +758,10 @@ label.local.storage=Local Storage
label.local=Local
label.login=Login
label.logout=Logout
-label.saml.login=SAML Login
+label.saml.login=CAFe Single Sign On
+label.saml.enable=Authorize 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/7310c2d4/client/tomcatconf/commands.properties.in
----------------------------------------------------------------------
diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in
index a87d167..a66a3dc 100644
--- a/client/tomcatconf/commands.properties.in
+++ b/client/tomcatconf/commands.properties.in
@@ -26,6 +26,9 @@ logout=15
samlSso=15
samlSlo=15
getSPMetadata=15
+listIdps=15
+authorizeSamlSso=7
+listSamlAuthorization=7
### Account commands
createAccount=7
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/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/7310c2d4/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/7310c2d4/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/7310c2d4/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/7310c2d4/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/7310c2d4/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/7310c2d4/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..1d005b2 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> getAllUsersByNameAndEntity(String username, String entity);
+
UserAccount getUserAccount(String username, Long domainId);
boolean validateUsernameInDomain(String username, Long domainId);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/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..a8d9e39 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,17 @@ public class UserAccountDaoImpl extends GenericDaoBase<UserAccountVO, Long> impl
}
@Override
+ public List<UserAccountVO> getAllUsersByNameAndEntity(String username, String entity) {
+ if (username == null) {
+ return null;
+ }
+ SearchCriteria<UserAccountVO> sc = createSearchCriteria();
+ sc.addAnd("username", SearchCriteria.Op.EQ, username);
+ sc.addAnd("externalEntity", SearchCriteria.Op.EQ, entity);
+ 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/7310c2d4/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/7310c2d4/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/7310c2d4/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..54ce418
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/AuthorizeSAMLSSOCmd.java
@@ -0,0 +1,105 @@
+// 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.domain.Domain;
+import com.cloud.user.Account;
+import com.cloud.user.UserAccount;
+import org.apache.cloudstack.acl.SecurityChecker;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+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 = false, responseHasSensitiveInfo = false)
+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() {
+ return Account.ACCOUNT_ID_SYSTEM;
+ }
+
+ @Override
+ public void execute() {
+ // Check permissions
+ UserAccount userAccount = _accountService.getUserAccountById(getId());
+ if (userAccount == null) {
+ throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR , "Unable to find a user account with the given ID");
+ }
+ Domain domain = _domainService.getDomain(userAccount.getDomainId());
+ Account account = _accountService.getAccount(userAccount.getAccountId());
+ _accountService.checkAccess(CallContext.current().getCallingAccount(), domain);
+ _accountService.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, true, account);
+
+ 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/7310c2d4/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..3a52151 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
@@ -14,7 +14,6 @@
// 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;
@@ -30,21 +29,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 +87,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 +133,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 +146,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 +180,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/7310c2d4/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..7d7c95e
--- /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 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/7310c2d4/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListSamlAuthorizationCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListSamlAuthorizationCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListSamlAuthorizationCmd.java
new file mode 100644
index 0000000..be958a1
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListSamlAuthorizationCmd.java
@@ -0,0 +1,95 @@
+// 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.user.Account;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.UserDao;
+import org.apache.cloudstack.acl.SecurityChecker;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.SamlAuthorizationResponse;
+import org.apache.cloudstack.api.response.UserResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+
+@APICommand(name = "listSamlAuthorization", description = "Lists authorized users who can used SAML SSO", responseObject = SamlAuthorizationResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+public class ListSamlAuthorizationCmd extends BaseListCmd {
+ public static final Logger s_logger = Logger.getLogger(ListSamlAuthorizationCmd.class.getName());
+ private static final String s_name = "listsamlauthorizationsresponse";
+
+ @Inject
+ private UserDao _userDao;
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+ @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, required = false, description = "User uuid")
+ private Long userId;
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return Account.ACCOUNT_ID_SYSTEM;
+ }
+
+ @Override
+ public void execute() {
+ List<UserVO> users = new ArrayList<UserVO>();
+ if (getUserId() != null) {
+ UserVO user = _userDao.getUser(getUserId());
+ if (user != null) {
+ Account account = _accountService.getAccount(user.getAccountId());
+ _accountService.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.ListEntry, true, account);
+ users.add(user);
+ }
+ } else if (CallContext.current().getCallingAccount().getType() == Account.ACCOUNT_TYPE_ADMIN) {
+ users = _userDao.listAll();
+ }
+
+ ListResponse<SamlAuthorizationResponse> response = new ListResponse<SamlAuthorizationResponse>();
+ List<SamlAuthorizationResponse> authorizationResponses = new ArrayList<SamlAuthorizationResponse>();
+ for (User user: users) {
+ SamlAuthorizationResponse authorizationResponse = new SamlAuthorizationResponse(user.getUuid(), user.getSource().equals(User.Source.SAML2), user.getExternalEntity());
+ authorizationResponse.setObjectName("samlauthorization");
+ authorizationResponses.add(authorizationResponse);
+ }
+ response.setResponses(authorizationResponses);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/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..b05ebf6 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
@@ -14,16 +14,14 @@
// 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.configuration.Config;
-import com.cloud.domain.Domain;
import com.cloud.exception.CloudAuthenticationException;
import com.cloud.user.Account;
import com.cloud.user.DomainManager;
import com.cloud.user.UserAccount;
+import com.cloud.user.UserAccountVO;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.utils.HttpUtils;
import com.cloud.utils.db.EntityManager;
@@ -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,44 @@ 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];
+ }
+
+ 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 + "/";
+ }
+ }
+
+ 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 redirectUrl = this.buildAuthnRequestUrl(idpUrl);
+ 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 +185,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 +194,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 +235,106 @@ 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 (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.");
+ if (username == null) {
+ username = SAMLUtils.getValueFromAssertions(processedSAMLResponse.getAssertions(), SAML2AuthManager.SAMLUserAttributeName.value());
}
- 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;
+ for (Assertion assertion: processedSAMLResponse.getAssertions()) {
+ if (assertion!= null && assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
+ session.setAttribute(SAMLPluginConstants.SAML_NAMEID, assertion.getSubject().getNameID().getValue());
+ break;
}
}
- 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 (assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
+ session.setAttribute(SAMLPluginConstants.SAML_NAMEID, assertion.getSubject().getNameID().getValue());
+ }
+ 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));
+ }
+
+ UserAccount userAccount = null;
+ List<UserAccountVO> possibleUserAccounts = _userAccountDao.getAllUsersByNameAndEntity(username, issuer.getValue());
+ if (possibleUserAccounts != null && possibleUserAccounts.size() > 0) {
+ if (possibleUserAccounts.size() == 1) {
+ userAccount = possibleUserAccounts.get(0);
+ } else if (possibleUserAccounts.size() > 1) {
+ if (domainId != null) {
+ userAccount = _userAccountDao.getUserAccount(username, domainId);
+ } else {
+ throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+ "You have accounts in multiple domains, please re-login by specifying the domain you want to log into.",
+ 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 || !_samlAuthManager.isUserAuthorized(userAccount.getId(), 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 +345,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));
}