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:29 UTC
[2/4] git commit: updated refs/heads/saml-pp-squashed to 7310c2d
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmdTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmdTest.java
deleted file mode 100644
index cb16f0c..0000000
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmdTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.utils.HttpUtils;
-import org.apache.cloudstack.api.ApiServerService;
-import org.apache.cloudstack.api.auth.APIAuthenticationType;
-import org.apache.cloudstack.saml.SAML2AuthManager;
-import org.apache.cloudstack.utils.auth.SAMLUtils;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import java.lang.reflect.Field;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-
-@RunWith(MockitoJUnitRunner.class)
-public class GetServiceProviderMetaDataCmdTest {
-
- @Mock
- ApiServerService apiServer;
-
- @Mock
- SAML2AuthManager samlAuthManager;
-
- @Mock
- HttpSession session;
-
- @Mock
- HttpServletResponse resp;
-
- @Mock
- HttpServletRequest req;
-
- @Test
- public void testAuthenticate() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, CertificateParsingException, CertificateEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {
- GetServiceProviderMetaDataCmd cmd = new GetServiceProviderMetaDataCmd();
-
- Field apiServerField = GetServiceProviderMetaDataCmd.class.getDeclaredField("_apiServer");
- apiServerField.setAccessible(true);
- apiServerField.set(cmd, apiServer);
-
- Field managerField = GetServiceProviderMetaDataCmd.class.getDeclaredField("_samlAuthManager");
- managerField.setAccessible(true);
- managerField.set(cmd, samlAuthManager);
-
- String spId = "someSPID";
- String url = "someUrl";
- X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
- Mockito.when(samlAuthManager.getServiceProviderId()).thenReturn(spId);
- Mockito.when(samlAuthManager.getIdpSigningKey()).thenReturn(cert);
- Mockito.when(samlAuthManager.getIdpSingleLogOutUrl()).thenReturn(url);
- Mockito.when(samlAuthManager.getSpSingleLogOutUrl()).thenReturn(url);
-
- String result = cmd.authenticate("command", null, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
- Assert.assertTrue(result.contains("md:EntityDescriptor"));
-
- Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getServiceProviderId();
- Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getSpSingleSignOnUrl();
- Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getSpSingleLogOutUrl();
- Mockito.verify(samlAuthManager, Mockito.never()).getIdpSingleSignOnUrl();
- Mockito.verify(samlAuthManager, Mockito.never()).getIdpSingleLogOutUrl();
- }
-
- @Test
- public void testGetAPIType() {
- Assert.assertTrue(new GetServiceProviderMetaDataCmd().getAPIType() == APIAuthenticationType.LOGIN_API);
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
index 30ecc93..00455b9 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
@@ -29,9 +29,10 @@ 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.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.saml.SAML2AuthManager;
-import org.apache.cloudstack.utils.auth.SAMLUtils;
+import org.apache.cloudstack.saml.SAMLPluginConstants;
+import org.apache.cloudstack.saml.SAMLProviderMetadata;
+import org.apache.cloudstack.saml.SAMLUtils;
import org.joda.time.DateTime;
import org.junit.Assert;
import org.junit.Test;
@@ -43,6 +44,7 @@ import org.opensaml.common.SAMLVersion;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AttributeStatement;
import org.opensaml.saml2.core.AuthnStatement;
+import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDType;
import org.opensaml.saml2.core.Response;
@@ -52,6 +54,7 @@ import org.opensaml.saml2.core.Subject;
import org.opensaml.saml2.core.impl.AssertionBuilder;
import org.opensaml.saml2.core.impl.AttributeStatementBuilder;
import org.opensaml.saml2.core.impl.AuthnStatementBuilder;
+import org.opensaml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml2.core.impl.ResponseBuilder;
import org.opensaml.saml2.core.impl.StatusBuilder;
@@ -62,6 +65,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
+import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
@@ -76,9 +80,6 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
SAML2AuthManager samlAuthManager;
@Mock
- ConfigurationDao configDao;
-
- @Mock
DomainManager domainMgr;
@Mock
@@ -104,6 +105,9 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
samlMessage.setID("foo");
samlMessage.setVersion(SAMLVersion.VERSION_20);
samlMessage.setIssueInstant(new DateTime(0));
+ Issuer issuer = new IssuerBuilder().buildObject();
+ issuer.setValue("MockedIssuer");
+ samlMessage.setIssuer(issuer);
Status status = new StatusBuilder().buildObject();
StatusCode statusCode = new StatusCodeBuilder().buildObject();
statusCode.setValue(StatusCode.SUCCESS_URI);
@@ -145,32 +149,33 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
domainMgrField.setAccessible(true);
domainMgrField.set(cmd, domainMgr);
- Field configDaoField = SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("_configDao");
- configDaoField.setAccessible(true);
- configDaoField.set(cmd, configDao);
-
Field userAccountDaoField = SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("_userAccountDao");
userAccountDaoField.setAccessible(true);
userAccountDaoField.set(cmd, userAccountDao);
String spId = "someSPID";
String url = "someUrl";
- X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
- Mockito.when(samlAuthManager.getServiceProviderId()).thenReturn(spId);
- Mockito.when(samlAuthManager.getIdpSigningKey()).thenReturn(null);
- Mockito.when(samlAuthManager.getIdpSingleSignOnUrl()).thenReturn(url);
- Mockito.when(samlAuthManager.getSpSingleSignOnUrl()).thenReturn(url);
+ KeyPair kp = SAMLUtils.generateRandomKeyPair();
+ X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp);
+
+ SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata();
+ providerMetadata.setEntityId("random");
+ providerMetadata.setSigningCertificate(cert);
+ providerMetadata.setEncryptionCertificate(cert);
+ providerMetadata.setKeyPair(kp);
+ providerMetadata.setSsoUrl("http://test.local");
+ providerMetadata.setSloUrl("http://test.local");
Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null);
- Mockito.when(configDao.getValue(Mockito.anyString())).thenReturn("someString");
Mockito.when(domain.getId()).thenReturn(1L);
Mockito.when(domainMgr.getDomain(Mockito.anyString())).thenReturn(domain);
UserAccountVO user = new UserAccountVO();
- user.setUsername(SAMLUtils.createSAMLId("someUID"));
user.setId(1000L);
Mockito.when(userAccountDao.getUserAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(user);
Mockito.when(apiServer.verifyUser(Mockito.anyLong())).thenReturn(false);
+ Mockito.when(samlAuthManager.getSPMetadata()).thenReturn(providerMetadata);
+ Mockito.when(samlAuthManager.getIdPMetadata(Mockito.anyString())).thenReturn(providerMetadata);
Map<String, Object[]> params = new HashMap<String, Object[]>();
@@ -179,16 +184,14 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
Mockito.verify(resp, Mockito.times(1)).sendRedirect(Mockito.anyString());
// SSO SAMLResponse verification test, this should throw ServerApiException for auth failure
- params.put(SAMLUtils.SAML_RESPONSE, new String[]{"Some String"});
+ params.put(SAMLPluginConstants.SAML_RESPONSE, new String[]{"Some String"});
Mockito.stub(cmd.processSAMLResponse(Mockito.anyString())).toReturn(buildMockResponse());
try {
cmd.authenticate("command", params, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
} catch (ServerApiException ignored) {
}
- Mockito.verify(configDao, Mockito.atLeastOnce()).getValue(Mockito.anyString());
- Mockito.verify(domainMgr, Mockito.times(1)).getDomain(Mockito.anyString());
- Mockito.verify(userAccountDao, Mockito.times(1)).getUserAccount(Mockito.anyString(), Mockito.anyLong());
- Mockito.verify(apiServer, Mockito.times(1)).verifyUser(Mockito.anyLong());
+ Mockito.verify(userAccountDao, Mockito.times(0)).getUserAccount(Mockito.anyString(), Mockito.anyLong());
+ Mockito.verify(apiServer, Mockito.times(0)).verifyUser(Mockito.anyLong());
}
@Test
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
index e9834c9..eff4b29 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
@@ -22,9 +22,8 @@ package org.apache.cloudstack.api.command;
import com.cloud.utils.HttpUtils;
import org.apache.cloudstack.api.ApiServerService;
import org.apache.cloudstack.api.auth.APIAuthenticationType;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.saml.SAML2AuthManager;
-import org.apache.cloudstack.utils.auth.SAMLUtils;
+import org.apache.cloudstack.saml.SAMLUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,9 +47,6 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
SAML2AuthManager samlAuthManager;
@Mock
- ConfigurationDao configDao;
-
- @Mock
HttpSession session;
@Mock
@@ -71,19 +67,10 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
managerField.setAccessible(true);
managerField.set(cmd, samlAuthManager);
- Field configDaoField = SAML2LogoutAPIAuthenticatorCmd.class.getDeclaredField("_configDao");
- configDaoField.setAccessible(true);
- configDaoField.set(cmd, configDao);
-
String spId = "someSPID";
String url = "someUrl";
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
- Mockito.when(samlAuthManager.getServiceProviderId()).thenReturn(spId);
- Mockito.when(samlAuthManager.getIdpSigningKey()).thenReturn(cert);
- Mockito.when(samlAuthManager.getIdpSingleLogOutUrl()).thenReturn(url);
- Mockito.when(samlAuthManager.getSpSingleLogOutUrl()).thenReturn(url);
Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null);
- Mockito.when(configDao.getValue(Mockito.anyString())).thenReturn("someString");
cmd.authenticate("command", null, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
Mockito.verify(resp, Mockito.times(1)).sendRedirect(Mockito.anyString());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/server/src/com/cloud/api/ApiServer.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java
index 6dcf48a..2ab1f7c 100755
--- a/server/src/com/cloud/api/ApiServer.java
+++ b/server/src/com/cloud/api/ApiServer.java
@@ -1062,8 +1062,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
final SecureRandom sesssionKeyRandom = new SecureRandom();
final byte sessionKeyBytes[] = new byte[20];
sesssionKeyRandom.nextBytes(sessionKeyBytes);
- final String sessionKey = Base64.encodeBase64String(sessionKeyBytes);
- session.setAttribute("sessionkey", sessionKey);
+ final String sessionKey = Base64.encodeBase64URLSafeString(sessionKeyBytes);
+ session.setAttribute(ApiConstants.SESSIONKEY, sessionKey);
return createLoginResponse(session);
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/server/src/com/cloud/api/ApiServlet.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/ApiServlet.java b/server/src/com/cloud/api/ApiServlet.java
index 8d34dfe..2bffc77 100644
--- a/server/src/com/cloud/api/ApiServlet.java
+++ b/server/src/com/cloud/api/ApiServlet.java
@@ -231,7 +231,7 @@ public class ApiServlet extends HttpServlet {
userId = (Long)session.getAttribute("userid");
final String account = (String)session.getAttribute("account");
final Object accountObj = session.getAttribute("accountobj");
- final String sessionKey = (String)session.getAttribute("sessionkey");
+ final String sessionKey = (String)session.getAttribute(ApiConstants.SESSIONKEY);
final String[] sessionKeyParam = (String[])params.get(ApiConstants.SESSIONKEY);
if ((sessionKeyParam == null) || (sessionKey == null) || !sessionKey.equals(sessionKeyParam[0])) {
try {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/server/src/com/cloud/configuration/Config.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java
index fc529b5..0d6a4e6 100755
--- a/server/src/com/cloud/configuration/Config.java
+++ b/server/src/com/cloud/configuration/Config.java
@@ -1362,78 +1362,6 @@ public enum Config {
"300000",
"The allowable clock difference in milliseconds between when an SSO login request is made and when it is received.",
null),
- SAMLIsPluginEnabled(
- "Advanced",
- ManagementServer.class,
- Boolean.class,
- "saml2.enabled",
- "false",
- "Set it to true to enable SAML SSO plugin",
- null),
- SAMLUserDomain(
- "Advanced",
- ManagementServer.class,
- String.class,
- "saml2.default.domainid",
- "1",
- "The default domain UUID to use when creating users from SAML SSO",
- null),
- SAMLCloudStackRedirectionUrl(
- "Advanced",
- ManagementServer.class,
- String.class,
- "saml2.redirect.url",
- "http://localhost:8080/client",
- "The CloudStack UI url the SSO should redirected to when successful",
- null),
- SAMLServiceProviderID(
- "Advanced",
- ManagementServer.class,
- String.class,
- "saml2.sp.id",
- "org.apache.cloudstack",
- "SAML2 Service Provider Identifier String",
- null),
- SAMLServiceProviderSingleSignOnURL(
- "Advanced",
- ManagementServer.class,
- String.class,
- "saml2.sp.sso.url",
- "http://localhost:8080/client/api?command=samlSso",
- "SAML2 CloudStack Service Provider Single Sign On URL",
- null),
- SAMLServiceProviderSingleLogOutURL(
- "Advanced",
- ManagementServer.class,
- String.class,
- "saml2.sp.slo.url",
- "http://localhost:8080/client/api?command=samlSlo",
- "SAML2 CloudStack Service Provider Single Log Out URL",
- null),
- SAMLIdentityProviderID(
- "Advanced",
- ManagementServer.class,
- String.class,
- "saml2.idp.id",
- "https://openidp.feide.no",
- "SAML2 Identity Provider Identifier String",
- null),
- SAMLIdentityProviderMetadataURL(
- "Advanced",
- ManagementServer.class,
- String.class,
- "saml2.idp.metadata.url",
- "https://openidp.feide.no/simplesaml/saml2/idp/metadata.php",
- "SAML2 Identity Provider Metadata XML Url",
- null),
- SAMLTimeout(
- "Advanced",
- ManagementServer.class,
- Long.class,
- "saml2.timeout",
- "30000",
- "SAML2 IDP Metadata Downloading and parsing etc. activity timeout in milliseconds",
- null),
//NetworkType("Hidden", ManagementServer.class, String.class, "network.type", "vlan", "The type of network that this deployment will use.", "vlan,direct"),
RouterRamSize("Hidden", NetworkOrchestrationService.class, Integer.class, "router.ram.size", "256", "Default RAM for router VM (in MB).", null),
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/setup/db/db/schema-451to452-cleanup.sql
----------------------------------------------------------------------
diff --git a/setup/db/db/schema-451to452-cleanup.sql b/setup/db/db/schema-451to452-cleanup.sql
new file mode 100644
index 0000000..9f5e62a
--- /dev/null
+++ b/setup/db/db/schema-451to452-cleanup.sql
@@ -0,0 +1,20 @@
+-- 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.
+
+--;
+-- Schema cleanup from 4.5.1 to 4.5.2;
+--;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/setup/db/db/schema-451to452.sql
----------------------------------------------------------------------
diff --git a/setup/db/db/schema-451to452.sql b/setup/db/db/schema-451to452.sql
new file mode 100644
index 0000000..5c89008
--- /dev/null
+++ b/setup/db/db/schema-451to452.sql
@@ -0,0 +1,35 @@
+-- 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.
+
+--;
+-- Schema upgrade from 4.5.1 to 4.5.2;
+--;
+
+DELETE FROM `cloud`.`configuration` WHERE name like 'saml%';
+
+ALTER TABLE `cloud`.`user` ADD COLUMN `external_entity` text DEFAULT NULL COMMENT "reference to external federation entity";
+
+DROP TABLE IF EXISTS `cloud`.`saml_token`;
+CREATE TABLE `cloud`.`saml_token` (
+ `id` bigint unsigned NOT NULL AUTO_INCREMENT,
+ `uuid` varchar(255) UNIQUE NOT NULL COMMENT 'The Authn Unique Id',
+ `domain_id` bigint unsigned DEFAULT NULL,
+ `entity` text NOT NULL COMMENT 'Identity Provider Entity Id',
+ `created` DATETIME NOT NULL,
+ PRIMARY KEY (`id`),
+ CONSTRAINT `fk_saml_token__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/tools/apidoc/gen_toc.py
----------------------------------------------------------------------
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index 95f06c8..4fac7ee 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -115,6 +115,9 @@ known_categories = {
'logout': 'Authentication',
'saml': 'Authentication',
'getSPMetadata': 'Authentication',
+ 'listIdps': 'Authentication',
+ 'authorizeSamlSso': 'Authentication',
+ 'listSamlAuthorization': 'Authentication',
'Capacity': 'System Capacity',
'NetworkDevice': 'Network Device',
'ExternalLoadBalancer': 'Ext Load Balancer',
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/css/cloudstack3.css
----------------------------------------------------------------------
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 0a2c57f..3539329 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -369,7 +369,7 @@ body.login {
.login .select-language select {
width: 260px;
border: 1px solid #808080;
- margin-top: 30px;
+ margin-top: 20px;
/*+border-radius:4px;*/
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
@@ -460,14 +460,12 @@ body.login {
background: transparent url(../images/sprites.png) -563px -747px;
cursor: pointer;
border: none;
- margin: 7px 120px 0 -1px;
text-align: center;
width: 60px;
height: 15px;
display: block;
color: #FFFFFF;
font-weight: bold;
- float: left;
text-indent: -1px;
/*+text-shadow:0px 1px 2px #000000;*/
-moz-text-shadow: 0px 1px 2px #000000;
@@ -12730,6 +12728,15 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it
background-position: -196px -704px;
}
+.configureSamlAuthorization .icon {
+ background-position: -165px -122px;
+}
+
+.configureSamlAuthorization:hover .icon {
+ background-position: -165px -704px;
+}
+
+
.viewConsole .icon {
background-position: -231px -2px;
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/dictionary.jsp
----------------------------------------------------------------------
diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp
index 317acfa..9d4baa2 100644
--- a/ui/dictionary.jsp
+++ b/ui/dictionary.jsp
@@ -141,6 +141,7 @@ dictionary = {
'label.action.cancel.maintenance.mode': '<fmt:message key="label.action.cancel.maintenance.mode" />',
'label.action.cancel.maintenance.mode.processing': '<fmt:message key="label.action.cancel.maintenance.mode.processing" />',
'label.action.change.password': '<fmt:message key="label.action.change.password" />',
+'label.action.configure.samlauthorization': '<fmt:message key="label.action.configure.samlauthorization" />',
'label.action.change.service': '<fmt:message key="label.action.change.service" />',
'label.action.change.service.processing': '<fmt:message key="label.action.change.service.processing" />',
'label.action.copy.ISO': '<fmt:message key="label.action.copy.ISO" />',
@@ -762,6 +763,9 @@ dictionary = {
'label.login': '<fmt:message key="label.login" />',
'label.logout': '<fmt:message key="label.logout" />',
'label.saml.login': '<fmt:message key="label.saml.login" />',
+'label.saml.enable': '<fmt:message key="label.saml.enable" />',
+'label.saml.entity': '<fmt:message key="label.saml.entity" />',
+'label.add.LDAP.account': '<fmt:message key="label.add.LDAP.account" />',
'label.lun': '<fmt:message key="label.lun" />',
'label.LUN.number': '<fmt:message key="label.LUN.number" />',
'label.make.project.owner': '<fmt:message key="label.make.project.owner" />',
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/index.jsp
----------------------------------------------------------------------
diff --git a/ui/index.jsp b/ui/index.jsp
index 4b601a5..0f8ef94 100644
--- a/ui/index.jsp
+++ b/ui/index.jsp
@@ -51,28 +51,44 @@
<form>
<div class="logo"></div>
<div class="fields">
- <!-- User name -->
- <div class="field username">
- <label for="username"><fmt:message key="label.username"/></label>
- <input type="text" name="username" class="required" />
+ <div id="login-dropdown">
+ <select id="login-options" style="width: 260px">
+ <option value="cloudstack-login">Local <fmt:message key="label.login"/></option>
+ </select>
</div>
- <!-- Password -->
- <div class="field password">
- <label for="password"><fmt:message key="label.password"/></label>
- <input type="password" name="password" class="required" autocomplete="off" />
+
+ <div id="saml-login">
+ <div class="field domain">
+ <label for="saml-domain"><fmt:message key="label.domain"/></label>
+ <input id="saml-domain" type="text" name="saml-domain" />
+ <input id="saml-login-submit" type="submit" value="<fmt:message key="label.login"/>" />
+ </div>
</div>
- <!-- Domain -->
- <div class="field domain">
- <label for="domain"><fmt:message key="label.domain"/></label>
- <input type="text" name="domain" />
+
+ <div id="cloudstack-login">
+ <!-- User name -->
+ <div class="field username">
+ <label for="username"><fmt:message key="label.username"/></label>
+ <input type="text" name="username" class="required" />
+ </div>
+ <!-- Password -->
+ <div class="field password">
+ <label for="password"><fmt:message key="label.password"/></label>
+ <input type="password" name="password" class="required" autocomplete="off" />
+ </div>
+ <!-- Domain -->
+ <div class="field domain">
+ <label for="domain"><fmt:message key="label.domain"/></label>
+ <input type="text" name="domain" />
+ </div>
+ <!-- Submit (login) -->
+ <input id="cloudstack-login-submit" type="submit" value="<fmt:message key="label.login"/>" />
</div>
- <!-- Submit (login) -->
- <input type="submit" value="<fmt:message key="label.login"/>" />
- <div id="saml-login"><input type="samlsubmit" value="<fmt:message key="label.saml.login"/>"/></div>
+
<!-- Select language -->
<div class="select-language">
<select name="language">
- <option value=""></option> <!-- when this blank option is selected, browser's default language will be used -->
+ <option value=""></option> <!-- when this blank option is selected, default language of the browser will be used -->
<option value="en"><fmt:message key="label.lang.english"/></option>
<option value="ja_JP"><fmt:message key="label.lang.japanese"/></option>
<option value="zh_CN"><fmt:message key="label.lang.chinese"/></option>
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/scripts/accounts.js
----------------------------------------------------------------------
diff --git a/ui/scripts/accounts.js b/ui/scripts/accounts.js
index bb85ebf..ad8ee61 100644
--- a/ui/scripts/accounts.js
+++ b/ui/scripts/accounts.js
@@ -1223,6 +1223,102 @@
}
},
+ configureSamlAuthorization: {
+ label: 'label.action.configure.samlauthorization',
+ messages: {
+ notification: function(args) {
+ return 'label.action.configure.samlauthorization';
+ }
+ },
+ action: {
+ custom: function(args) {
+ var start = args.start;
+ var complete = args.complete;
+ var context = args.context;
+
+ if (g_idpList) {
+ $.ajax({
+ url: createURL('listSamlAuthorization'),
+ data: {
+ userid: context.users[0].id,
+ },
+ success: function(json) {
+ var authorization = json.listsamlauthorizationsresponse.samlauthorization[0];
+ cloudStack.dialog.createForm({
+ form: {
+ title: 'label.action.configure.samlauthorization',
+ fields: {
+ samlEnable: {
+ label: 'label.saml.enable',
+ docID: 'helpSamlEnable',
+ isBoolean: true,
+ isChecked: authorization.status,
+ validation: {
+ required: false
+ }
+ },
+ samlEntity: {
+ label: 'label.saml.entity',
+ docID: 'helpSamlEntity',
+ validation: {
+ required: false
+ },
+ select: function(args) {
+ var items = [];
+ $(g_idpList).each(function() {
+ items.push({
+ id: this.id,
+ description: this.orgName
+ });
+ });
+ args.response.success({
+ data: items
+ });
+ args.$select.change(function() {
+ $('select[name="samlEntity"] option[value="' + authorization.idpid + '"]').attr("selected", "selected");
+ });
+ }
+ }
+ }
+ },
+ after: function(args) {
+ start();
+ var enableSaml = false;
+ var idpId = '';
+ if (args.data.hasOwnProperty('samlEnable')) {
+ enableSaml = (args.data.samlEnable === 'on');
+ }
+ if (args.data.hasOwnProperty('samlEntity')) {
+ idpId = args.data.samlEntity;
+ }
+ $.ajax({
+ url: createURL('authorizeSamlSso'),
+ data: {
+ userid: context.users[0].id,
+ enable: enableSaml,
+ entityid: idpId
+ },
+ type: "POST",
+ success: function(json) {
+ complete();
+ },
+ error: function(json) {
+ complete({ error: parseXMLHttpResponse(json) });
+ }
+ });
+ }
+ });
+ },
+ error: function(json) {
+ complete({ error: parseXMLHttpResponse(json) });
+ }
+ });
+
+ }
+ }
+ }
+ },
+
generateKeys: {
label: 'label.action.generate.keys',
messages: {
@@ -1520,6 +1616,9 @@
allowedActions.push("edit");
allowedActions.push("changePassword");
allowedActions.push("generateKeys");
+ if (g_idpList) {
+ allowedActions.push("configureSamlAuthorization");
+ }
if (!(jsonObj.domain == "ROOT" && jsonObj.account == "admin" && jsonObj.accounttype == 1)) { //if not system-generated default admin account user
if (jsonObj.state == "enabled")
allowedActions.push("disable");
@@ -1541,6 +1640,9 @@
allowedActions.push("changePassword");
allowedActions.push("generateKeys");
+ if (g_idpList) {
+ allowedActions.push("configureSamlAuthorization");
+ }
}
}
return allowedActions;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/scripts/accountsWizard.js
----------------------------------------------------------------------
diff --git a/ui/scripts/accountsWizard.js b/ui/scripts/accountsWizard.js
index 82e7eab..7ea5eaa 100644
--- a/ui/scripts/accountsWizard.js
+++ b/ui/scripts/accountsWizard.js
@@ -162,8 +162,34 @@
validation: {
required: false
}
+ },
+ samlEnable: {
+ label: 'label.saml.enable',
+ docID: 'helpSamlEnable',
+ isBoolean: true,
+ validation: {
+ required: false
+ }
+ },
+ samlEntity: {
+ label: 'label.saml.entity',
+ docID: 'helpSamlEntity',
+ validation: {
+ required: false
+ },
+ select: function(args) {
+ var items = [];
+ $(g_idpList).each(function() {
+ items.push({
+ id: this.id,
+ description: this.orgName
+ });
+ });
+ args.response.success({
+ data: items
+ });
+ }
}
-
},
action: function(args) {
@@ -218,6 +244,18 @@
array1.push("&group=" + args.groupname);
}
+ var authorizeUsersForSamlSSO = function (users, entity) {
+ for (var i = 0; i < users.length; i++) {
+ $.ajax({
+ url: createURL('authorizeSamlSso&enable=true&userid=' + users[i].id + "&entityid=" + entity),
+ error: function(XMLHttpResponse) {
+ args.response.error(parseXMLHttpResponse(XMLHttpResponse));
+ }
+ });
+ }
+ return;
+ };
+
if (ldapStatus) {
if (args.groupname) {
$.ajax({
@@ -225,6 +263,13 @@
dataType: "json",
type: "POST",
async: false,
+ success: function (json) {
+ if (json.ldapuserresponse && args.data.samlEnable && args.data.samlEnable === 'on') {
+ cloudStack.dialog.notice({
+ message: "Unable to find users IDs to enable SAML Single Sign On, kindly enable it manually."
+ });
+ }
+ },
error: function(XMLHttpResponse) {
args.response.error(parseXMLHttpResponse(XMLHttpResponse));
}
@@ -235,6 +280,14 @@
dataType: "json",
type: "POST",
async: false,
+ success: function(json) {
+ if (args.data.samlEnable && args.data.samlEnable === 'on') {
+ var users = json.createaccountresponse.account.user;
+ var entity = args.data.samlEntity;
+ if (users && entity)
+ authorizeUsersForSamlSSO(users, entity);
+ }
+ },
error: function(XMLHttpResponse) {
args.response.error(parseXMLHttpResponse(XMLHttpResponse));
}
@@ -246,6 +299,14 @@
dataType: "json",
type: "POST",
async: false,
+ success: function(json) {
+ if (args.data.samlEnable && args.data.samlEnable === 'on') {
+ var users = json.createaccountresponse.account.user;
+ var entity = args.data.samlEntity;
+ if (users && entity)
+ authorizeUsersForSamlSSO(users, entity);
+ }
+ },
error: function(XMLHttpResponse) {
args.response.error(parseXMLHttpResponse(XMLHttpResponse));
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/scripts/cloudStack.js
----------------------------------------------------------------------
diff --git a/ui/scripts/cloudStack.js b/ui/scripts/cloudStack.js
index 479b162..a701c41 100644
--- a/ui/scripts/cloudStack.js
+++ b/ui/scripts/cloudStack.js
@@ -115,7 +115,7 @@
cookieValue = cookieValue.slice(1, cookieValue.length-1);
$.cookie(cookieName, cookieValue, { expires: 1 });
}
- return cookieValue;
+ return decodeURIComponent(cookieValue);
};
unBoxCookieValue('sessionkey');
// if sessionkey cookie exists use this to set g_sessionKey
@@ -353,6 +353,17 @@
},
samlLoginAction: function(args) {
+ g_sessionKey = null;
+ g_username = null;
+ g_account = null;
+ g_domainid = null;
+ g_timezoneoffset = null;
+ g_timezone = null;
+ g_supportELB = null;
+ g_kvmsnapshotenabled = null;
+ g_regionsecondaryenabled = null;
+ g_loginCmdText = null;
+
$.cookie('JSESSIONID', null);
$.cookie('sessionkey', null);
$.cookie('username', null);
@@ -360,7 +371,14 @@
$.cookie('domainid', null);
$.cookie('role', null);
$.cookie('timezone', null);
- window.location.href = createURL('samlSso');
+ var url = 'samlSso';
+ if (args.data.idpid) {
+ url = url + '&idpid=' + args.data.idpid;
+ }
+ if (args.data.domain) {
+ url = url + '&domain=' + args.data.domain;
+ }
+ window.location.href = createURL(url);
},
// Show cloudStack main UI widget
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/scripts/docs.js
----------------------------------------------------------------------
diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js
index d38bcf7..ed94ccc 100755
--- a/ui/scripts/docs.js
+++ b/ui/scripts/docs.js
@@ -1247,6 +1247,14 @@ cloudStack.docs = {
desc: 'The group name from which you want to import LDAP users',
externalLink: ''
},
+ helpSamlEnable: {
+ desc: 'Enable SAML Single Sign On for the user(s)',
+ externalLink: ''
+ },
+ helpSamlEntity: {
+ desc: 'Choose the SAML Identity Provider Entity ID with which you want to enable the Single Sign On for the user(s)',
+ externalLink: ''
+ },
helpVpcOfferingName: {
desc: 'Any desired name for the VPC offering',
externalLink: ''
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/scripts/sharedFunctions.js
----------------------------------------------------------------------
diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js
index 1e1514b..75860dc 100644
--- a/ui/scripts/sharedFunctions.js
+++ b/ui/scripts/sharedFunctions.js
@@ -32,6 +32,7 @@ var g_regionsecondaryenabled = null;
var g_userPublicTemplateEnabled = "true";
var g_cloudstackversion = null;
var g_queryAsyncJobResultInterval = 3000;
+var g_idpList = null;
//keyboard keycode
var keycode_Enter = 13;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/scripts/ui-custom/accountsWizard.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui-custom/accountsWizard.js b/ui/scripts/ui-custom/accountsWizard.js
index 3259227..cfbe930 100644
--- a/ui/scripts/ui-custom/accountsWizard.js
+++ b/ui/scripts/ui-custom/accountsWizard.js
@@ -271,6 +271,11 @@
delete args.informationNotInLdap.ldapGroupName;
}
+ if (g_idpList == null) {
+ delete args.informationNotInLdap.samlEnable;
+ delete args.informationNotInLdap.samlEntity;
+ }
+
var informationNotInLdap = cloudStack.dialog.createForm({
context: context,
noDialog: true,
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/ui/scripts/ui-custom/login.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui-custom/login.js b/ui/scripts/ui-custom/login.js
index 1f82c82..6d7d3fe 100644
--- a/ui/scripts/ui-custom/login.js
+++ b/ui/scripts/ui-custom/login.js
@@ -94,7 +94,7 @@
$inputs.filter(':first').addClass('first-input').focus();
// Login action
- $login.find('input[type=submit]').click(function() {
+ $login.find('#cloudstack-login-submit').click(function() {
if (!$form.valid()) return false;
var data = cloudStack.serializeForm($form);
@@ -121,27 +121,85 @@
});
// SAML Login action
- $login.find('input[type=samlsubmit]').click(function() {
+ $login.find('#saml-login-submit').click(function() {
args.samlLoginAction({
+ data: {'idpid': $login.find('#login-options').find(':selected').val(),
+ 'domain': $login.find('#saml-domain').val()}
});
+ return false;
});
// Show SAML button if only SP is configured
- $login.find("#saml-login").hide();
+ $login.find('#login-dropdown').hide();
+ $login.find('#saml-login').hide();
+ $login.find('#cloudstack-login').hide();
+
+ var toggleLoginView = function (selectedOption) {
+ if (selectedOption === '') {
+ $login.find('#saml-login').hide();
+ $login.find('#cloudstack-login').hide();
+ } else if (selectedOption === 'cloudstack-login') {
+ $login.find('#saml-login').hide();
+ $login.find('#cloudstack-login').show();
+ } else {
+ $login.find('#saml-login').show();
+ $login.find('#cloudstack-login').hide();
+ }
+ };
+
+ $login.find('#login-options').change(function() {
+ var selectedOption = $login.find('#login-options').find(':selected').val();
+ toggleLoginView(selectedOption);
+ if (selectedOption && selectedOption !== '') {
+ $.cookie('login-option', selectedOption);
+ }
+ });
+
$.ajax({
- type: "GET",
- url: createURL("getSPMetadata"),
- dataType: "json",
+ type: 'GET',
+ url: createURL('listIdps'),
+ dataType: 'json',
async: false,
success: function(data, textStatus, xhr) {
if (xhr.status === 200) {
- $login.find('#saml-login').show();
+ $login.find('#login-dropdown').show();
} else {
- $login.find('#saml-login').hide();
+ $login.find('#cloudstack-login').show();
+ return;
+ }
+
+ $login.find('#login-options')
+ .append($('<option>', {
+ value: '',
+ text: '--- Select Identity Provider -- ',
+ selected: true
+ }));
+
+ if (data.listidpsresponse && data.listidpsresponse.idp) {
+ var idpList = data.listidpsresponse.idp.sort(function (a, b) {
+ return a.orgName.localeCompare(b.orgName);
+ });
+ g_idpList = idpList;
+ $.each(idpList, function(index, idp) {
+ $login.find('#login-options')
+ .append($('<option>', {
+ value: idp.id,
+ text: idp.orgName
+ }));
+ });
+ }
+
+ var loginOption = $.cookie('login-option');
+ if (loginOption) {
+ var option = $login.find('#login-options option[value="' + loginOption + '"]');
+ if (option.length > 0) {
+ option.prop('selected', true);
+ toggleLoginView(loginOption);
+ }
}
},
error: function(xhr) {
- $login.find('#saml-login').hide();
+ $login.find('#cloudstack-login').show();
},
});
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/utils/src/org/apache/cloudstack/utils/auth/SAMLUtils.java
----------------------------------------------------------------------
diff --git a/utils/src/org/apache/cloudstack/utils/auth/SAMLUtils.java b/utils/src/org/apache/cloudstack/utils/auth/SAMLUtils.java
deleted file mode 100644
index a6d2d34..0000000
--- a/utils/src/org/apache/cloudstack/utils/auth/SAMLUtils.java
+++ /dev/null
@@ -1,330 +0,0 @@
-//
-// 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.utils.auth;
-
-import com.cloud.utils.HttpUtils;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.log4j.Logger;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.x509.X509V1CertificateGenerator;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.opensaml.Configuration;
-import org.opensaml.common.SAMLVersion;
-import org.opensaml.common.xml.SAMLConstants;
-import org.opensaml.saml2.core.AuthnContextClassRef;
-import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
-import org.opensaml.saml2.core.AuthnRequest;
-import org.opensaml.saml2.core.Issuer;
-import org.opensaml.saml2.core.LogoutRequest;
-import org.opensaml.saml2.core.NameID;
-import org.opensaml.saml2.core.NameIDPolicy;
-import org.opensaml.saml2.core.NameIDType;
-import org.opensaml.saml2.core.RequestedAuthnContext;
-import org.opensaml.saml2.core.Response;
-import org.opensaml.saml2.core.SessionIndex;
-import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
-import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
-import org.opensaml.saml2.core.impl.IssuerBuilder;
-import org.opensaml.saml2.core.impl.LogoutRequestBuilder;
-import org.opensaml.saml2.core.impl.NameIDBuilder;
-import org.opensaml.saml2.core.impl.NameIDPolicyBuilder;
-import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
-import org.opensaml.saml2.core.impl.SessionIndexBuilder;
-import org.opensaml.xml.ConfigurationException;
-import org.opensaml.xml.XMLObject;
-import org.opensaml.xml.io.Marshaller;
-import org.opensaml.xml.io.MarshallingException;
-import org.opensaml.xml.io.Unmarshaller;
-import org.opensaml.xml.io.UnmarshallerFactory;
-import org.opensaml.xml.io.UnmarshallingException;
-import org.opensaml.xml.signature.SignatureConstants;
-import org.opensaml.xml.util.Base64;
-import org.opensaml.xml.util.XMLHelper;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.xml.sax.SAXException;
-
-import javax.security.auth.x500.X500Principal;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
-import java.net.URLEncoder;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
-
-public class SAMLUtils {
- public static final Logger s_logger = Logger.getLogger(SAMLUtils.class);
-
- public static final String SAML_RESPONSE = "SAMLResponse";
- public static final String SAML_NS = "SAML-";
- public static final String SAML_NAMEID = "SAML_NAMEID";
- public static final String SAML_SESSION = "SAML_SESSION";
- public static final String SAMLSP_KEYPAIR = "SAMLSP_KEYPAIR";
- public static final String SAMLSP_X509CERT = "SAMLSP_X509CERT";
-
- public static String createSAMLId(String uid) {
- if (uid == null) {
- return null;
- }
- String hash = DigestUtils.sha256Hex(uid);
- String samlUuid = SAML_NS + hash;
- return samlUuid.substring(0, 40);
- }
-
- public static boolean checkSAMLUser(String uuid, String username) {
- if (uuid == null || uuid.isEmpty() || username == null || username.isEmpty()) {
- return false;
- }
- return uuid.startsWith(SAML_NS) && createSAMLId(username).equals(uuid);
- }
-
- public static String generateSecureRandomId() {
- return new BigInteger(160, new SecureRandom()).toString(32);
- }
-
- public static AuthnRequest buildAuthnRequestObject(String spId, String idpUrl, String consumerUrl) {
- String authnId = generateSecureRandomId();
- // Issuer object
- IssuerBuilder issuerBuilder = new IssuerBuilder();
- Issuer issuer = issuerBuilder.buildObject();
- issuer.setValue(spId);
-
- // NameIDPolicy
- NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
- NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
- nameIdPolicy.setFormat(NameIDType.PERSISTENT);
- nameIdPolicy.setSPNameQualifier(spId);
- nameIdPolicy.setAllowCreate(true);
-
- // AuthnContextClass
- AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
- AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject(
- SAMLConstants.SAML20_NS,
- "AuthnContextClassRef", "saml");
- authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
-
- // AuthnContex
- RequestedAuthnContextBuilder requestedAuthnContextBuilder = new RequestedAuthnContextBuilder();
- RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
- requestedAuthnContext
- .setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
- requestedAuthnContext.getAuthnContextClassRefs().add(
- authnContextClassRef);
-
- // Creation of AuthRequestObject
- AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
- AuthnRequest authnRequest = authRequestBuilder.buildObject();
- authnRequest.setID(authnId);
- authnRequest.setDestination(idpUrl);
- authnRequest.setVersion(SAMLVersion.VERSION_20);
- authnRequest.setForceAuthn(false);
- authnRequest.setIsPassive(false);
- authnRequest.setIssuer(issuer);
- authnRequest.setIssueInstant(new DateTime());
- authnRequest.setProtocolBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
- authnRequest.setAssertionConsumerServiceURL(consumerUrl);
- authnRequest.setProviderName(spId);
- authnRequest.setNameIDPolicy(nameIdPolicy);
- authnRequest.setRequestedAuthnContext(requestedAuthnContext);
-
- return authnRequest;
- }
-
- public static LogoutRequest buildLogoutRequest(String logoutUrl, String spId, NameID sessionNameId, String sessionIndex) {
- IssuerBuilder issuerBuilder = new IssuerBuilder();
- Issuer issuer = issuerBuilder.buildObject();
- issuer.setValue(spId);
-
- SessionIndex sessionIndexElement = new SessionIndexBuilder().buildObject();
- sessionIndexElement.setSessionIndex(sessionIndex);
-
- NameID nameID = new NameIDBuilder().buildObject();
- nameID.setValue(sessionNameId.getValue());
- nameID.setFormat(sessionNameId.getFormat());
-
- LogoutRequest logoutRequest = new LogoutRequestBuilder().buildObject();
- logoutRequest.setID(generateSecureRandomId());
- logoutRequest.setDestination(logoutUrl);
- logoutRequest.setVersion(SAMLVersion.VERSION_20);
- logoutRequest.setIssueInstant(new DateTime());
- logoutRequest.setIssuer(issuer);
- logoutRequest.getSessionIndexes().add(sessionIndexElement);
- logoutRequest.setNameID(nameID);
- return logoutRequest;
- }
-
- public static String encodeSAMLRequest(XMLObject authnRequest)
- throws MarshallingException, IOException {
- Marshaller marshaller = Configuration.getMarshallerFactory()
- .getMarshaller(authnRequest);
- Element authDOM = marshaller.marshall(authnRequest);
- StringWriter requestWriter = new StringWriter();
- XMLHelper.writeNode(authDOM, requestWriter);
- String requestMessage = requestWriter.toString();
- Deflater deflater = new Deflater(Deflater.DEFLATED, true);
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
- deflaterOutputStream.write(requestMessage.getBytes());
- deflaterOutputStream.close();
- String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(), Base64.DONT_BREAK_LINES);
- encodedRequestMessage = URLEncoder.encode(encodedRequestMessage, HttpUtils.UTF_8).trim();
- return encodedRequestMessage;
- }
-
- public static Response decodeSAMLResponse(String responseMessage)
- throws ConfigurationException, ParserConfigurationException,
- SAXException, IOException, UnmarshallingException {
- DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
- documentBuilderFactory.setNamespaceAware(true);
- DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
- byte[] base64DecodedResponse = Base64.decode(responseMessage);
- Document document = docBuilder.parse(new ByteArrayInputStream(base64DecodedResponse));
- Element element = document.getDocumentElement();
- UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
- Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
- return (Response) unmarshaller.unmarshall(element);
- }
-
- public static String generateSAMLRequestSignature(String urlEncodedString, PrivateKey signingKey)
- throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, UnsupportedEncodingException {
- if (signingKey == null) {
- return urlEncodedString;
- }
- String url = urlEncodedString + "&SigAlg=" + URLEncoder.encode(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1, HttpUtils.UTF_8);
- Signature signature = Signature.getInstance("SHA1withRSA");
- signature.initSign(signingKey);
- signature.update(url.getBytes());
- String signatureString = Base64.encodeBytes(signature.sign(), Base64.DONT_BREAK_LINES);
- if (signatureString != null) {
- return url + "&Signature=" + URLEncoder.encode(signatureString, HttpUtils.UTF_8);
- }
- return url;
- }
-
- public static KeyFactory getKeyFactory() {
- KeyFactory keyFactory = null;
- try {
- Security.addProvider(new BouncyCastleProvider());
- keyFactory = KeyFactory.getInstance("RSA", "BC");
- } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
- s_logger.error("Unable to create KeyFactory:" + e.getMessage());
- }
- return keyFactory;
- }
-
- public static String savePublicKey(PublicKey key) {
- try {
- KeyFactory keyFactory = SAMLUtils.getKeyFactory();
- if (keyFactory == null) return null;
- X509EncodedKeySpec spec = keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
- return new String(org.bouncycastle.util.encoders.Base64.encode(spec.getEncoded()));
- } catch (InvalidKeySpecException e) {
- s_logger.error("Unable to create KeyFactory:" + e.getMessage());
- }
- return null;
- }
-
- public static String savePrivateKey(PrivateKey key) {
- try {
- KeyFactory keyFactory = SAMLUtils.getKeyFactory();
- if (keyFactory == null) return null;
- PKCS8EncodedKeySpec spec = keyFactory.getKeySpec(key,
- PKCS8EncodedKeySpec.class);
- return new String(org.bouncycastle.util.encoders.Base64.encode(spec.getEncoded()));
- } catch (InvalidKeySpecException e) {
- s_logger.error("Unable to create KeyFactory:" + e.getMessage());
- }
- return null;
- }
-
- public static PublicKey loadPublicKey(String publicKey) {
- byte[] sigBytes = org.bouncycastle.util.encoders.Base64.decode(publicKey);
- X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(sigBytes);
- KeyFactory keyFact = SAMLUtils.getKeyFactory();
- if (keyFact == null)
- return null;
- try {
- return keyFact.generatePublic(x509KeySpec);
- } catch (InvalidKeySpecException e) {
- s_logger.error("Unable to create PrivateKey from privateKey string:" + e.getMessage());
- }
- return null;
- }
-
- public static PrivateKey loadPrivateKey(String privateKey) {
- byte[] sigBytes = org.bouncycastle.util.encoders.Base64.decode(privateKey);
- PKCS8EncodedKeySpec pkscs8KeySpec = new PKCS8EncodedKeySpec(sigBytes);
- KeyFactory keyFact = SAMLUtils.getKeyFactory();
- if (keyFact == null)
- return null;
- try {
- return keyFact.generatePrivate(pkscs8KeySpec);
- } catch (InvalidKeySpecException e) {
- s_logger.error("Unable to create PrivateKey from privateKey string:" + e.getMessage());
- }
- return null;
- }
-
- public static KeyPair generateRandomKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException {
- Security.addProvider(new BouncyCastleProvider());
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
- keyPairGenerator.initialize(4096, new SecureRandom());
- return keyPairGenerator.generateKeyPair();
- }
-
- public static X509Certificate generateRandomX509Certificate(KeyPair keyPair) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateEncodingException, SignatureException, InvalidKeyException {
- DateTime now = DateTime.now(DateTimeZone.UTC);
- X500Principal dnName = new X500Principal("CN=ApacheCloudStack");
- X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
- certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
- certGen.setSubjectDN(dnName);
- certGen.setIssuerDN(dnName);
- certGen.setNotBefore(now.minusDays(1).toDate());
- certGen.setNotAfter(now.plusYears(3).toDate());
- certGen.setPublicKey(keyPair.getPublic());
- certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
- return certGen.generate(keyPair.getPrivate(), "BC");
- }
-
-}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/7310c2d4/utils/test/org/apache/cloudstack/utils/auth/SAMLUtilsTest.java
----------------------------------------------------------------------
diff --git a/utils/test/org/apache/cloudstack/utils/auth/SAMLUtilsTest.java b/utils/test/org/apache/cloudstack/utils/auth/SAMLUtilsTest.java
deleted file mode 100644
index bebfd13..0000000
--- a/utils/test/org/apache/cloudstack/utils/auth/SAMLUtilsTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-//
-// 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.utils.auth;
-
-import junit.framework.TestCase;
-import org.junit.Test;
-import org.opensaml.saml2.core.AuthnRequest;
-import org.opensaml.saml2.core.LogoutRequest;
-import org.opensaml.saml2.core.NameID;
-import org.opensaml.saml2.core.impl.NameIDBuilder;
-
-import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
-public class SAMLUtilsTest extends TestCase {
-
- @Test
- public void testSAMLId() throws Exception {
- assertEquals(SAMLUtils.createSAMLId(null), null);
- assertEquals(SAMLUtils.createSAMLId("someUserName"), "SAML-305e19dd2581f33fd90b3949298ec8b17de");
-
- assertTrue(SAMLUtils.checkSAMLUser(SAMLUtils.createSAMLId("someUserName"), "someUserName"));
- assertFalse(SAMLUtils.checkSAMLUser(SAMLUtils.createSAMLId("someUserName"), "someOtherUserName"));
- assertFalse(SAMLUtils.checkSAMLUser(SAMLUtils.createSAMLId(null), "someOtherUserName"));
- assertFalse(SAMLUtils.checkSAMLUser("randomUID", "randomUID"));
- assertFalse(SAMLUtils.checkSAMLUser(null, null));
- }
-
- @Test
- public void testGenerateSecureRandomId() throws Exception {
- assertTrue(SAMLUtils.generateSecureRandomId().length() > 0);
- }
-
- @Test
- public void testBuildAuthnRequestObject() throws Exception {
- String consumerUrl = "http://someurl.com";
- String idpUrl = "http://idp.domain.example";
- String spId = "cloudstack";
- AuthnRequest req = SAMLUtils.buildAuthnRequestObject(spId, idpUrl, consumerUrl);
- assertEquals(req.getAssertionConsumerServiceURL(), consumerUrl);
- assertEquals(req.getDestination(), idpUrl);
- assertEquals(req.getIssuer().getValue(), spId);
- }
-
- @Test
- public void testBuildLogoutRequest() throws Exception {
- String logoutUrl = "http://logoutUrl";
- String spId = "cloudstack";
- String sessionIndex = "12345";
- String nameIdString = "someNameID";
- NameID sessionNameId = new NameIDBuilder().buildObject();
- sessionNameId.setValue(nameIdString);
- LogoutRequest req = SAMLUtils.buildLogoutRequest(logoutUrl, spId, sessionNameId, sessionIndex);
- assertEquals(req.getDestination(), logoutUrl);
- assertEquals(req.getIssuer().getValue(), spId);
- assertEquals(req.getNameID().getValue(), nameIdString);
- assertEquals(req.getSessionIndexes().get(0).getSessionIndex(), sessionIndex);
- }
-
- @Test
- public void testX509Helpers() throws Exception {
- KeyPair keyPair = SAMLUtils.generateRandomKeyPair();
-
- String privateKeyString = SAMLUtils.savePrivateKey(keyPair.getPrivate());
- String publicKeyString = SAMLUtils.savePublicKey(keyPair.getPublic());
-
- PrivateKey privateKey = SAMLUtils.loadPrivateKey(privateKeyString);
- PublicKey publicKey = SAMLUtils.loadPublicKey(publicKeyString);
-
- assertTrue(privateKey.equals(keyPair.getPrivate()));
- assertTrue(publicKey.equals(keyPair.getPublic()));
- }
-}
\ No newline at end of file