You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2015/06/03 20:30:57 UTC
[2/2] git commit: updated refs/heads/saml-production-grade to b8b21aa
CLOUDSTACK-8458: Support SAML Federation, where there may be more than one IdP
- New datastructure to hold metadata of SP or IdP
- Recursive processing of IdP metadata
- Fix login/logout APIs to get new interface and metadata data structure
- Add org/contact information to metadata
- Add new API: listIdps that returns list of all discovered IdPs
- Refactor and cleanup code and tests
Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/b8b21aa9
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/b8b21aa9
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/b8b21aa9
Branch: refs/heads/saml-production-grade
Commit: b8b21aa99dae19522ce5fb2d52c4c8f1bfb2744d
Parents: 79ef7f3
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Wed Jun 3 20:26:07 2015 +0200
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Wed Jun 3 20:29:43 2015 +0200
----------------------------------------------------------------------
.../org/apache/cloudstack/api/ApiConstants.java | 2 +-
.../command/GetServiceProviderMetaDataCmd.java | 65 ++++-
.../cloudstack/api/command/ListIdpsCmd.java | 107 +++++++
.../command/SAML2LoginAPIAuthenticatorCmd.java | 79 +++--
.../command/SAML2LogoutAPIAuthenticatorCmd.java | 19 +-
.../cloudstack/api/response/IdpResponse.java | 62 ++++
.../cloudstack/saml/SAML2AuthManager.java | 51 ++--
.../cloudstack/saml/SAML2AuthManagerImpl.java | 287 ++++++++++++-------
.../cloudstack/saml/SAML2UserAuthenticator.java | 4 +-
.../cloudstack/saml/SAMLPluginConstants.java | 27 ++
.../cloudstack/saml/SAMLProviderMetadata.java | 122 ++++++++
.../org/apache/cloudstack/saml/SAMLUtils.java | 39 ++-
.../GetServiceProviderMetaDataCmdTest.java | 11 -
.../cloudstack/SAML2UserAuthenticatorTest.java | 4 +-
.../org/apache/cloudstack/SAMLUtilsTest.java | 8 +-
.../SAML2LoginAPIAuthenticatorCmdTest.java | 7 +-
.../SAML2LogoutAPIAuthenticatorCmdTest.java | 13 -
ui/css/cloudstack3.css | 2 +-
18 files changed, 657 insertions(+), 252 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/api/src/org/apache/cloudstack/api/ApiConstants.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java
index b6aed6f..1b5e0cb 100755
--- a/api/src/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/org/apache/cloudstack/api/ApiConstants.java
@@ -515,7 +515,7 @@ public class ApiConstants {
public static final String VMPROFILE_ID = "vmprofileid";
public static final String VMGROUP_ID = "vmgroupid";
public static final String CS_URL = "csurl";
- public static final String IDP_URL = "idpurl";
+ public static final String IDP_ID = "idpid";
public static final String SCALEUP_POLICY_IDS = "scaleuppolicyids";
public static final String SCALEDOWN_POLICY_IDS = "scaledownpolicyids";
public static final String SCALEUP_POLICIES = "scaleuppolicies";
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java
index 0997f48..0af02d5 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java
@@ -30,21 +30,36 @@ import org.apache.cloudstack.api.auth.APIAuthenticator;
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
import org.apache.cloudstack.api.response.SAMLMetaDataResponse;
import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLProviderMetadata;
import org.apache.log4j.Logger;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.NameIDType;
import org.opensaml.saml2.metadata.AssertionConsumerService;
+import org.opensaml.saml2.metadata.ContactPerson;
+import org.opensaml.saml2.metadata.ContactPersonTypeEnumeration;
+import org.opensaml.saml2.metadata.EmailAddress;
import org.opensaml.saml2.metadata.EntityDescriptor;
+import org.opensaml.saml2.metadata.GivenName;
import org.opensaml.saml2.metadata.KeyDescriptor;
+import org.opensaml.saml2.metadata.LocalizedString;
import org.opensaml.saml2.metadata.NameIDFormat;
+import org.opensaml.saml2.metadata.Organization;
+import org.opensaml.saml2.metadata.OrganizationName;
+import org.opensaml.saml2.metadata.OrganizationURL;
import org.opensaml.saml2.metadata.SPSSODescriptor;
import org.opensaml.saml2.metadata.SingleLogoutService;
import org.opensaml.saml2.metadata.impl.AssertionConsumerServiceBuilder;
+import org.opensaml.saml2.metadata.impl.ContactPersonBuilder;
+import org.opensaml.saml2.metadata.impl.EmailAddressBuilder;
import org.opensaml.saml2.metadata.impl.EntityDescriptorBuilder;
+import org.opensaml.saml2.metadata.impl.GivenNameBuilder;
import org.opensaml.saml2.metadata.impl.KeyDescriptorBuilder;
import org.opensaml.saml2.metadata.impl.NameIDFormatBuilder;
+import org.opensaml.saml2.metadata.impl.OrganizationBuilder;
+import org.opensaml.saml2.metadata.impl.OrganizationNameBuilder;
+import org.opensaml.saml2.metadata.impl.OrganizationURLBuilder;
import org.opensaml.saml2.metadata.impl.SPSSODescriptorBuilder;
import org.opensaml.saml2.metadata.impl.SingleLogoutServiceBuilder;
import org.opensaml.xml.ConfigurationException;
@@ -73,6 +88,7 @@ import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
@APICommand(name = "getSPMetadata", description = "Returns SAML2 CloudStack Service Provider MetaData", responseObject = SAMLMetaDataResponse.class, entityType = {})
@@ -118,8 +134,10 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent
params, responseType));
}
+ final SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata();
+
EntityDescriptor spEntityDescriptor = new EntityDescriptorBuilder().buildObject();
- spEntityDescriptor.setEntityID(_samlAuthManager.getServiceProviderId());
+ spEntityDescriptor.setEntityID(spMetadata.getEntityId());
SPSSODescriptor spSSODescriptor = new SPSSODescriptorBuilder().buildObject();
spSSODescriptor.setWantAssertionsSigned(true);
@@ -129,19 +147,23 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent
keyInfoGeneratorFactory.setEmitEntityCertificate(true);
KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance();
+ KeyDescriptor signKeyDescriptor = new KeyDescriptorBuilder().buildObject();
+ signKeyDescriptor.setUse(UsageType.SIGNING);
+
KeyDescriptor encKeyDescriptor = new KeyDescriptorBuilder().buildObject();
encKeyDescriptor.setUse(UsageType.ENCRYPTION);
- KeyDescriptor signKeyDescriptor = new KeyDescriptorBuilder().buildObject();
- signKeyDescriptor.setUse(UsageType.SIGNING);
+ BasicX509Credential signingCredential = new BasicX509Credential();
+ signingCredential.setEntityCertificate(spMetadata.getSigningCertificate());
+
+ BasicX509Credential encryptionCredential = new BasicX509Credential();
+ encryptionCredential.setEntityCertificate(spMetadata.getEncryptionCertificate());
- BasicX509Credential credential = new BasicX509Credential();
- credential.setEntityCertificate(_samlAuthManager.getSpX509Certificate());
try {
- encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential));
- signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential));
- spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor);
+ signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(signingCredential));
+ encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(encryptionCredential));
spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor);
+ spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor);
} catch (SecurityException e) {
s_logger.warn("Unable to add SP X509 descriptors:" + e.getMessage());
}
@@ -162,28 +184,47 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent
assertionConsumerService.setIndex(1);
assertionConsumerService.setIsDefault(true);
assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
- assertionConsumerService.setLocation(_samlAuthManager.getSpSingleSignOnUrl());
+ assertionConsumerService.setLocation(spMetadata.getSsoUrl());
spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService);
AssertionConsumerService assertionConsumerService2 = new AssertionConsumerServiceBuilder().buildObject();
assertionConsumerService2.setIndex(2);
assertionConsumerService2.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
- assertionConsumerService2.setLocation(_samlAuthManager.getSpSingleSignOnUrl());
+ assertionConsumerService2.setLocation(spMetadata.getSsoUrl());
spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService2);
SingleLogoutService ssoService = new SingleLogoutServiceBuilder().buildObject();
ssoService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
- ssoService.setLocation(_samlAuthManager.getSpSingleLogOutUrl());
+ ssoService.setLocation(spMetadata.getSloUrl());
spSSODescriptor.getSingleLogoutServices().add(ssoService);
SingleLogoutService ssoService2 = new SingleLogoutServiceBuilder().buildObject();
ssoService2.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
- ssoService2.setLocation(_samlAuthManager.getSpSingleLogOutUrl());
+ ssoService2.setLocation(spMetadata.getSloUrl());
spSSODescriptor.getSingleLogoutServices().add(ssoService2);
spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS);
spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor);
+ ContactPerson contactPerson = new ContactPersonBuilder().buildObject();
+ GivenName givenName = new GivenNameBuilder().buildObject();
+ givenName.setName(spMetadata.getContactPersonName());
+ EmailAddress emailAddress = new EmailAddressBuilder().buildObject();
+ emailAddress.setAddress(spMetadata.getContactPersonEmail());
+ contactPerson.setType(ContactPersonTypeEnumeration.TECHNICAL);
+ contactPerson.setGivenName(givenName);
+ contactPerson.getEmailAddresses().add(emailAddress);
+ spEntityDescriptor.getContactPersons().add(contactPerson);
+
+ Organization organization = new OrganizationBuilder().buildObject();
+ OrganizationName organizationName = new OrganizationNameBuilder().buildObject();
+ organizationName.setName(new LocalizedString(spMetadata.getOrganizationName(), Locale.getDefault().getLanguage()));
+ OrganizationURL organizationURL = new OrganizationURLBuilder().buildObject();
+ organizationURL.setURL(new LocalizedString(spMetadata.getOrganizationUrl(), Locale.getDefault().getLanguage()));
+ organization.getOrganizationNames().add(organizationName);
+ organization.getURLs().add(organizationURL);
+ spEntityDescriptor.setOrganization(organization);
+
StringWriter stringWriter = new StringWriter();
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListIdpsCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListIdpsCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListIdpsCmd.java
new file mode 100644
index 0000000..4786a62
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListIdpsCmd.java
@@ -0,0 +1,107 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command;
+
+import com.cloud.api.response.ApiResponseSerializer;
+import com.cloud.user.Account;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.ApiServerService;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.auth.APIAuthenticationType;
+import org.apache.cloudstack.api.auth.APIAuthenticator;
+import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.api.response.IdpResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLProviderMetadata;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@APICommand(name = "listIdps", description = "Returns list of discovered SAML 2.0 Identity Providers", responseObject = IdpResponse.class, entityType = {})
+public class ListIdpsCmd extends BaseCmd implements APIAuthenticator {
+ public static final Logger s_logger = Logger.getLogger(ListIdpsCmd.class.getName());
+ private static final String s_name = "listidpsresponse";
+
+ @Inject
+ ApiServerService _apiServer;
+
+ SAML2AuthManager _samlAuthManager;
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return Account.ACCOUNT_TYPE_NORMAL;
+ }
+
+ @Override
+ public void execute() throws ServerApiException {
+ // We should never reach here
+ throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
+ }
+
+ @Override
+ public String authenticate(String command, Map<String, Object[]> params, HttpSession session, String remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
+ auditTrailSb.append("=== SAML List IdPs ===");
+ ListResponse<IdpResponse> response = new ListResponse<IdpResponse>();
+ List<IdpResponse> idpResponseList = new ArrayList<IdpResponse>();
+ for (SAMLProviderMetadata metadata: _samlAuthManager.getAllIdPMetadata()) {
+ IdpResponse idpResponse = new IdpResponse();
+ idpResponse.setId(metadata.getEntityId());
+ idpResponse.setOrgName(metadata.getOrganizationName());
+ idpResponse.setOrgUrl(metadata.getOrganizationUrl());
+ idpResponse.setObjectName("idp");
+ idpResponseList.add(idpResponse);
+ }
+ response.setResponses(idpResponseList, idpResponseList.size());
+ response.setResponseName(getCommandName());
+ return ApiResponseSerializer.toSerializedString(response, responseType);
+ }
+
+ @Override
+ public APIAuthenticationType getAPIType() {
+ return APIAuthenticationType.LOGIN_API;
+ }
+
+ @Override
+ public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
+ for (PluggableAPIAuthenticator authManager: authenticators) {
+ if (authManager != null && authManager instanceof SAML2AuthManager) {
+ _samlAuthManager = (SAML2AuthManager) authManager;
+ }
+ }
+ if (_samlAuthManager == null) {
+ s_logger.error("No suitable Pluggable Authentication Manager found for SAML2 Login Cmd");
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
index 3fee5b2..ee7bfc9 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
@@ -39,12 +39,14 @@ import org.apache.cloudstack.api.auth.APIAuthenticator;
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
import org.apache.cloudstack.api.response.LoginCmdResponse;
import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLPluginConstants;
+import org.apache.cloudstack.saml.SAMLProviderMetadata;
import org.apache.cloudstack.saml.SAMLUtils;
import org.apache.log4j.Logger;
import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.core.Assertion;
-import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.encryption.Decrypter;
@@ -52,7 +54,6 @@ import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.encryption.DecryptionException;
import org.opensaml.xml.encryption.EncryptedKeyResolver;
import org.opensaml.xml.encryption.InlineEncryptedKeyResolver;
-import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.security.SecurityHelper;
import org.opensaml.xml.security.credential.Credential;
@@ -72,9 +73,6 @@ import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.FactoryConfigurationError;
import java.io.IOException;
import java.net.URLEncoder;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
import java.util.List;
import java.util.Map;
@@ -86,8 +84,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
- @Parameter(name = ApiConstants.IDP_URL, type = CommandType.STRING, description = "Identity Provider SSO HTTP-Redirect binding URL", required = true)
- private String idpUrl;
+ @Parameter(name = ApiConstants.IDP_ID, type = CommandType.STRING, description = "Identity Provider Entity ID", required = true)
+ private String idpId;
@Inject
ApiServerService _apiServer;
@@ -104,8 +102,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
- public String getIdpUrl() {
- return idpUrl;
+ public String getIdpId() {
+ return idpId;
}
/////////////////////////////////////////////////////
@@ -128,30 +126,6 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
}
- private String buildAuthnRequestUrl(String idpUrl) {
- String spId = _samlAuthManager.getServiceProviderId();
- String consumerUrl = _samlAuthManager.getSpSingleSignOnUrl();
- String identityProviderUrl = _samlAuthManager.getIdpSingleSignOnUrl();
-
- if (idpUrl != null) {
- identityProviderUrl = idpUrl;
- }
-
- String redirectUrl = "";
- try {
- DefaultBootstrap.bootstrap();
- AuthnRequest authnRequest = SAMLUtils.buildAuthnRequestObject(spId, identityProviderUrl, consumerUrl);
- PrivateKey privateKey = null;
- if (_samlAuthManager.getSpKeyPair() != null) {
- privateKey = _samlAuthManager.getSpKeyPair().getPrivate();
- }
- redirectUrl = identityProviderUrl + "?" + SAMLUtils.generateSAMLRequestSignature("SAMLRequest=" + SAMLUtils.encodeSAMLRequest(authnRequest), privateKey);
- } catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException | NoSuchAlgorithmException | InvalidKeyException | java.security.SignatureException e) {
- s_logger.error("SAML AuthnRequest message building error: " + e.getMessage());
- }
- return redirectUrl;
- }
-
public Response processSAMLResponse(String responseMessage) {
Response responseObject = null;
try {
@@ -168,12 +142,19 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
public String authenticate(final String command, final Map<String, Object[]> params, final HttpSession session, final String remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
try {
if (!params.containsKey("SAMLResponse") && !params.containsKey("SAMLart")) {
- String idpUrl = null;
- final String[] idps = (String[])params.get(ApiConstants.IDP_URL);
+ String idpId = null;
+ final String[] idps = (String[])params.get(ApiConstants.IDP_ID);
if (idps != null && idps.length > 0) {
- idpUrl = idps[0];
+ idpId = idps[0];
+ }
+ SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata();
+ SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(idpId);
+ if (idpMetadata == null) {
+ throw new ServerApiException(ApiErrorCode.PARAM_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
+ "IdP ID (" + idpId + ") is not found in our list of supported IdPs, cannot proceed.",
+ params, responseType));
}
- String redirectUrl = this.buildAuthnRequestUrl(idpUrl);
+ String redirectUrl = SAMLUtils.buildAuthnRequestUrl(spMetadata, idpMetadata);
resp.sendRedirect(redirectUrl);
return "";
} if (params.containsKey("SAMLart")) {
@@ -181,7 +162,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
"SAML2 HTTP Artifact Binding is not supported",
params, responseType));
} else {
- final String samlResponse = ((String[])params.get(SAMLUtils.SAML_RESPONSE))[0];
+ final String samlResponse = ((String[])params.get(SAMLPluginConstants.SAML_RESPONSE))[0];
Response processedSAMLResponse = this.processSAMLResponse(samlResponse);
String statusCode = processedSAMLResponse.getStatus().getStatusCode().getValue();
if (!statusCode.equals(StatusCode.SUCCESS_URI)) {
@@ -206,10 +187,17 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
s_logger.error("The default domain ID for SAML users is not set correct, it should be a UUID. ROOT domain will be used.");
}
+ Issuer issuer = processedSAMLResponse.getIssuer();
+ SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata();
+ SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(issuer.getValue());
+
+ // Set IdpId for this session
+ session.setAttribute(SAMLPluginConstants.SAML_IDPID, issuer.getValue());
+
Signature sig = processedSAMLResponse.getSignature();
- if (_samlAuthManager.getIdpSigningKey() != null && sig != null) {
+ if (idpMetadata.getSigningCertificate() != null && sig != null) {
BasicX509Credential credential = new BasicX509Credential();
- credential.setEntityCertificate(_samlAuthManager.getIdpSigningKey());
+ credential.setEntityCertificate(idpMetadata.getSigningCertificate());
SignatureValidator validator = new SignatureValidator(credential);
try {
validator.validate(sig);
@@ -223,9 +211,10 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
if (username == null) {
username = SAMLUtils.getValueFromAssertions(processedSAMLResponse.getAssertions(), SAML2AuthManager.SAMLUserAttributeName.value());
}
- if (_samlAuthManager.getIdpEncryptionKey() != null && _samlAuthManager.getSpKeyPair() != null && _samlAuthManager.getSpKeyPair().getPrivate() != null) {
- Credential credential = SecurityHelper.getSimpleCredential(_samlAuthManager.getIdpEncryptionKey().getPublicKey(),
- _samlAuthManager.getSpKeyPair().getPrivate());
+ if (idpMetadata.getEncryptionCertificate() != null && spMetadata != null
+ && spMetadata.getKeyPair() != null && spMetadata.getKeyPair().getPrivate() != null) {
+ Credential credential = SecurityHelper.getSimpleCredential(idpMetadata.getEncryptionCertificate().getPublicKey(),
+ spMetadata.getKeyPair().getPrivate());
StaticKeyInfoCredentialResolver keyInfoResolver = new StaticKeyInfoCredentialResolver(credential);
EncryptedKeyResolver keyResolver = new InlineEncryptedKeyResolver();
Decrypter decrypter = new Decrypter(null, keyInfoResolver, keyResolver);
@@ -243,9 +232,9 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
continue;
}
Signature encSig = assertion.getSignature();
- if (_samlAuthManager.getIdpSigningKey() != null && encSig != null) {
+ if (idpMetadata.getSigningCertificate() != null && encSig != null) {
BasicX509Credential sigCredential = new BasicX509Credential();
- sigCredential.setEntityCertificate(_samlAuthManager.getIdpSigningKey());
+ sigCredential.setEntityCertificate(idpMetadata.getSigningCertificate());
SignatureValidator validator = new SignatureValidator(sigCredential);
try {
validator.validate(encSig);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
index 18ff630..844176d 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
@@ -27,13 +27,13 @@ import org.apache.cloudstack.api.auth.APIAuthenticationType;
import org.apache.cloudstack.api.auth.APIAuthenticator;
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
import org.apache.cloudstack.api.response.LogoutCmdResponse;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLPluginConstants;
+import org.apache.cloudstack.saml.SAMLProviderMetadata;
import org.apache.cloudstack.saml.SAMLUtils;
import org.apache.log4j.Logger;
import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.core.LogoutRequest;
-import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.xml.ConfigurationException;
@@ -58,8 +58,7 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
@Inject
ApiServerService _apiServer;
- @Inject
- ConfigurationDao _configDao;
+
SAML2AuthManager _samlAuthManager;
/////////////////////////////////////////////////////
@@ -109,7 +108,7 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
if (params != null && params.containsKey("SAMLResponse")) {
try {
- final String samlResponse = ((String[])params.get(SAMLUtils.SAML_RESPONSE))[0];
+ final String samlResponse = ((String[])params.get(SAMLPluginConstants.SAML_RESPONSE))[0];
Response processedSAMLResponse = SAMLUtils.decodeSAMLResponse(samlResponse);
String statusCode = processedSAMLResponse.getStatus().getStatusCode().getValue();
if (!statusCode.equals(StatusCode.SUCCESS_URI)) {
@@ -127,19 +126,19 @@ public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthen
return responseString;
}
- NameID nameId = (NameID) session.getAttribute(SAMLUtils.SAML_NAMEID);
- String sessionIndex = (String) session.getAttribute(SAMLUtils.SAML_SESSION);
- if (nameId == null || sessionIndex == null) {
+ String idpId = (String) session.getAttribute(SAMLPluginConstants.SAML_IDPID);
+ SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(idpId);
+ if (idpMetadata == null) {
try {
resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value());
} catch (IOException ignored) {
}
return responseString;
}
- LogoutRequest logoutRequest = SAMLUtils.buildLogoutRequest(_samlAuthManager.getIdpSingleLogOutUrl(), _samlAuthManager.getServiceProviderId(), nameId, sessionIndex);
+ LogoutRequest logoutRequest = SAMLUtils.buildLogoutRequest(idpMetadata.getSloUrl(), _samlAuthManager.getSPMetadata().getEntityId());
try {
- String redirectUrl = _samlAuthManager.getIdpSingleLogOutUrl() + "?SAMLRequest=" + SAMLUtils.encodeSAMLRequest(logoutRequest);
+ String redirectUrl = idpMetadata.getSloUrl() + "?SAMLRequest=" + SAMLUtils.encodeSAMLRequest(logoutRequest);
resp.sendRedirect(redirectUrl);
} catch (MarshallingException | IOException e) {
s_logger.error("SAML SLO error: " + e.getMessage());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/IdpResponse.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/IdpResponse.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/IdpResponse.java
new file mode 100644
index 0000000..d95cc33
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/IdpResponse.java
@@ -0,0 +1,62 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.response;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+
+public class IdpResponse extends AuthenticationCmdResponse {
+ @SerializedName("id")
+ @Param(description = "The IdP Entity ID")
+ private String id;
+
+ @SerializedName("orgName")
+ @Param(description = "The IdP Organization Name")
+ private String orgName;
+
+ @SerializedName("orgUrl")
+ @Param(description = "The IdP Organization URL")
+ private String orgUrl;
+
+ public IdpResponse() {
+ super();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getOrgName() {
+ return orgName;
+ }
+
+ public void setOrgName(String orgName) {
+ this.orgName = orgName;
+ }
+
+ public String getOrgUrl() {
+ return orgUrl;
+ }
+
+ public void setOrgUrl(String orgUrl) {
+ this.orgUrl = orgUrl;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
index 468e9df..6c856d5 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
@@ -20,22 +20,27 @@ package org.apache.cloudstack.saml;
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
import org.apache.cloudstack.framework.config.ConfigKey;
-import java.security.KeyPair;
-import java.security.cert.X509Certificate;
+import java.util.Collection;
public interface SAML2AuthManager extends PluggableAPIAuthenticator {
public static final ConfigKey<Boolean> SAMLIsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.enabled", "false",
"Indicates whether SAML SSO plugin is enabled or not", true);
- public static final ConfigKey<String> SAMLUserAttributeName = new ConfigKey<String>("Advanced", String.class, "saml2.user.attribute", "uid",
- "Attribute name to be looked for in SAML response that will contain the username", true);
+ public static final ConfigKey<String> SAMLServiceProviderID = new ConfigKey<String>("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack",
+ "SAML2 Service Provider Identifier String", true);
- public static final ConfigKey<String> SAMLDefaultDomain = new ConfigKey<String>("Advanced", String.class, "saml2.default.domainid", "1",
- "The default domain UUID to use if domain information is not found while authenticating users", true);
+ public static final ConfigKey<String> SAMLServiceProviderContactPersonName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.person", "CloudStack Developers",
+ "SAML2 Service Provider Contact Person Name", true);
- public static final ConfigKey<String> SAMLCloudStackRedirectionUrl = new ConfigKey<String>("Advanced", String.class, "saml2.redirect.url", "http://localhost:8080/client",
- "The CloudStack UI url the SSO should redirected to when successful", true);
+ public static final ConfigKey<String> SAMLServiceProviderContactEmail = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.email", "dev@cloudstack.apache.org",
+ "SAML2 Service Provider Contact Email Address", true);
+
+ public static final ConfigKey<String> SAMLServiceProviderOrgName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.name", "Apache CloudStack",
+ "SAML2 Service Provider Organization Name", true);
+
+ public static final ConfigKey<String> SAMLServiceProviderOrgUrl = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.url", "http://cloudstack.apache.org",
+ "SAML2 Service Provider Organization URL", true);
public static final ConfigKey<String> SAMLServiceProviderSingleSignOnURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.sso.url", "http://localhost:8080/client/api?command=samlSso",
"SAML2 CloudStack Service Provider Single Sign On URL", true);
@@ -43,29 +48,25 @@ public interface SAML2AuthManager extends PluggableAPIAuthenticator {
public static final ConfigKey<String> SAMLServiceProviderSingleLogOutURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.slo.url", "http://localhost:8080/client/api?command=samlSlo",
"SAML2 CloudStack Service Provider Single Log Out URL", true);
- public static final ConfigKey<String> SAMLServiceProviderID = new ConfigKey<String>("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack",
- "SAML2 Service Provider Identifier String", true);
+ public static final ConfigKey<String> SAMLCloudStackRedirectionUrl = new ConfigKey<String>("Advanced", String.class, "saml2.redirect.url", "http://localhost:8080/client",
+ "The CloudStack UI url the SSO should redirected to when successful", true);
+
+ public static final ConfigKey<String> SAMLUserAttributeName = new ConfigKey<String>("Advanced", String.class, "saml2.user.attribute", "uid",
+ "Attribute name to be looked for in SAML response that will contain the username", true);
+
+ public static final ConfigKey<String> SAMLDefaultDomain = new ConfigKey<String>("Advanced", String.class, "saml2.default.domainid", "1",
+ "The default domain UUID to use if domain information is not found while authenticating users", true);
public static final ConfigKey<String> SAMLIdentityProviderMetadataURL = new ConfigKey<String>("Advanced", String.class, "saml2.idp.metadata.url", "https://openidp.feide.no/simplesaml/saml2/idp/metadata.php",
"SAML2 Identity Provider Metadata XML Url", true);
- public static final ConfigKey<String> SAMLIdentityProviderId = new ConfigKey<String>("Advanced", String.class, "saml2.idp.id", "https://openidp.feide.no",
- "SAML2 Identity Provider Metadata XML Url", true);
+ public static final ConfigKey<String> SAMLDefaultIdentityProviderId = new ConfigKey<String>("Advanced", String.class, "saml2.default.idpid", "https://openidp.feide.no",
+ "The default IdP entity ID to use only in case of multiple IdPs", true);
public static final ConfigKey<Integer> SAMLTimeout = new ConfigKey<Integer>("Advanced", Integer.class, "saml2.timeout", "30000",
"SAML2 IDP Metadata Downloading and parsing etc. activity timeout in milliseconds", true);
- public String getServiceProviderId();
- public String getIdentityProviderId();
-
- public X509Certificate getIdpSigningKey();
- public X509Certificate getIdpEncryptionKey();
- public X509Certificate getSpX509Certificate();
- public KeyPair getSpKeyPair();
-
- public String getSpSingleSignOnUrl();
- public String getIdpSingleSignOnUrl();
-
- public String getSpSingleLogOutUrl();
- public String getIdpSingleLogOutUrl();
+ public SAMLProviderMetadata getSPMetadata();
+ public SAMLProviderMetadata getIdPMetadata(String entityId);
+ public Collection<SAMLProviderMetadata> getAllIdPMetadata();
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
index 6849eb8..6797bed 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
@@ -19,25 +19,32 @@ package org.apache.cloudstack.saml;
import com.cloud.utils.component.AdapterBase;
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
+import org.apache.cloudstack.api.command.ListIdpsCmd;
import org.apache.cloudstack.api.command.SAML2LoginAPIAuthenticatorCmd;
import org.apache.cloudstack.api.command.SAML2LogoutAPIAuthenticatorCmd;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
import org.apache.cloudstack.framework.security.keystore.KeystoreVO;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.xml.SAMLConstants;
+import org.opensaml.saml2.metadata.ContactPerson;
+import org.opensaml.saml2.metadata.EmailAddress;
+import org.opensaml.saml2.metadata.EntitiesDescriptor;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml2.metadata.KeyDescriptor;
+import org.opensaml.saml2.metadata.OrganizationDisplayName;
+import org.opensaml.saml2.metadata.OrganizationName;
+import org.opensaml.saml2.metadata.OrganizationURL;
import org.opensaml.saml2.metadata.SingleLogoutService;
import org.opensaml.saml2.metadata.SingleSignOnService;
import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.xml.ConfigurationException;
+import org.opensaml.xml.XMLObject;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.security.credential.UsageType;
import org.opensaml.xml.security.keyinfo.KeyInfoHelper;
@@ -63,33 +70,25 @@ import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@Component
@Local(value = {SAML2AuthManager.class, PluggableAPIAuthenticator.class})
public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManager, Configurable {
private static final Logger s_logger = Logger.getLogger(SAML2AuthManagerImpl.class);
- private String serviceProviderId;
- private String identityProviderId;
+ private SAMLProviderMetadata _spMetadata = new SAMLProviderMetadata();
+ private Map<String, SAMLProviderMetadata> _idpMetadataMap = new HashMap<String, SAMLProviderMetadata>();
- private X509Certificate idpSigningKey;
- private X509Certificate idpEncryptionKey;
- private X509Certificate spX509Key;
- private KeyPair spKeyPair;
-
- private String spSingleSignOnUrl;
private String idpSingleSignOnUrl;
-
- private String spSingleLogOutUrl;
private String idpSingleLogOutUrl;
private HTTPMetadataProvider idpMetaDataProvider;
@Inject
- ConfigurationDao _configDao;
-
- @Inject
private KeystoreDao _ksDao;
@Override
@@ -103,24 +102,34 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
return super.start();
}
- private boolean setup() {
- KeystoreVO keyStoreVO = _ksDao.findByName(SAMLUtils.SAMLSP_KEYPAIR);
+ private boolean initSP() {
+ KeystoreVO keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
if (keyStoreVO == null) {
try {
KeyPair keyPair = SAMLUtils.generateRandomKeyPair();
- _ksDao.save(SAMLUtils.SAMLSP_KEYPAIR, SAMLUtils.savePrivateKey(keyPair.getPrivate()), SAMLUtils.savePublicKey(keyPair.getPublic()), "samlsp-keypair");
- keyStoreVO = _ksDao.findByName(SAMLUtils.SAMLSP_KEYPAIR);
+ _ksDao.save(SAMLPluginConstants.SAMLSP_KEYPAIR, SAMLUtils.savePrivateKey(keyPair.getPrivate()), SAMLUtils.savePublicKey(keyPair.getPublic()), "samlsp-keypair");
+ keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
+ s_logger.info("No SAML keystore found, created and saved a new Service Provider keypair");
} catch (NoSuchProviderException | NoSuchAlgorithmException e) {
- s_logger.error("Unable to create and save SAML keypair");
+ s_logger.error("Unable to create and save SAML keypair: " + e.toString());
}
}
+ String spId = SAMLServiceProviderID.value();
+ String spSsoUrl = SAMLServiceProviderSingleSignOnURL.value();
+ String spSloUrl = SAMLServiceProviderSingleLogOutURL.value();
+ String spOrgName = SAMLServiceProviderOrgName.value();
+ String spOrgUrl = SAMLServiceProviderOrgUrl.value();
+ String spContactPersonName = SAMLServiceProviderContactPersonName.value();
+ String spContactPersonEmail = SAMLServiceProviderContactEmail.value();
+ KeyPair spKeyPair = null;
+ X509Certificate spX509Key = null;
if (keyStoreVO != null) {
PrivateKey privateKey = SAMLUtils.loadPrivateKey(keyStoreVO.getCertificate());
PublicKey publicKey = SAMLUtils.loadPublicKey(keyStoreVO.getKey());
if (privateKey != null && publicKey != null) {
spKeyPair = new KeyPair(publicKey, privateKey);
- KeystoreVO x509VO = _ksDao.findByName(SAMLUtils.SAMLSP_X509CERT);
+ KeystoreVO x509VO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_X509CERT);
if (x509VO == null) {
try {
spX509Key = SAMLUtils.generateRandomX509Certificate(spKeyPair);
@@ -128,7 +137,7 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
ObjectOutput out = new ObjectOutputStream(bos);
out.writeObject(spX509Key);
out.flush();
- _ksDao.save(SAMLUtils.SAMLSP_X509CERT, Base64.encodeBase64String(bos.toByteArray()), "", "samlsp-x509cert");
+ _ksDao.save(SAMLPluginConstants.SAMLSP_X509CERT, Base64.encodeBase64String(bos.toByteArray()), "", "samlsp-x509cert");
bos.close();
} catch (NoSuchAlgorithmException | NoSuchProviderException | CertificateEncodingException | SignatureException | InvalidKeyException | IOException e) {
s_logger.error("SAML Plugin won't be able to use X509 signed authentication");
@@ -145,52 +154,107 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
}
}
}
+ if (spKeyPair != null && spX509Key != null
+ && spId != null && spSsoUrl != null && spSloUrl != null
+ && spOrgName != null && spOrgUrl != null
+ && spContactPersonName != null && spContactPersonEmail != null) {
+ _spMetadata.setEntityId(spId);
+ _spMetadata.setOrganizationName(spOrgName);
+ _spMetadata.setOrganizationUrl(spOrgUrl);
+ _spMetadata.setContactPersonName(spContactPersonName);
+ _spMetadata.setContactPersonEmail(spContactPersonEmail);
+ _spMetadata.setSsoUrl(spSsoUrl);
+ _spMetadata.setSloUrl(spSloUrl);
+ _spMetadata.setKeyPair(spKeyPair);
+ _spMetadata.setSigningCertificate(spX509Key);
+ _spMetadata.setEncryptionCertificate(spX509Key);
+ return true;
+ }
+ return false;
+ }
- this.serviceProviderId = SAMLServiceProviderID.value();
- this.identityProviderId = SAMLIdentityProviderId.value();
-
- this.spSingleSignOnUrl = SAMLServiceProviderSingleSignOnURL.value();
- this.spSingleLogOutUrl = SAMLServiceProviderSingleLogOutURL.value();
-
- String idpMetaDataUrl = SAMLIdentityProviderMetadataURL.value();
-
- int tolerance = 30000;
- tolerance = SAMLTimeout.value();
-
- try {
- DefaultBootstrap.bootstrap();
- idpMetaDataProvider = new HTTPMetadataProvider(idpMetaDataUrl, tolerance);
- idpMetaDataProvider.setRequireValidMetadata(true);
- idpMetaDataProvider.setParserPool(new BasicParserPool());
- idpMetaDataProvider.initialize();
+ private void addIdpToMap(EntityDescriptor descriptor, Map<String, SAMLProviderMetadata> idpMap) {
+ SAMLProviderMetadata idpMetadata = new SAMLProviderMetadata();
+ idpMetadata.setEntityId(descriptor.getEntityID());
+ s_logger.debug("Adding IdP to the list of discovered IdPs: " + descriptor.getEntityID());
+ if (descriptor.getOrganization() != null) {
+ if (descriptor.getOrganization().getDisplayNames() != null) {
+ for (OrganizationDisplayName orgName : descriptor.getOrganization().getDisplayNames()) {
+ if (orgName != null && orgName.getName() != null) {
+ idpMetadata.setOrganizationName(orgName.getName().getLocalString());
+ break;
+ }
+ }
+ }
+ if (idpMetadata.getOrganizationName() == null && descriptor.getOrganization().getOrganizationNames() != null) {
+ for (OrganizationName orgName : descriptor.getOrganization().getOrganizationNames()) {
+ if (orgName != null && orgName.getName() != null) {
+ idpMetadata.setOrganizationName(orgName.getName().getLocalString());
+ break;
+ }
+ }
+ }
+ if (descriptor.getOrganization().getURLs() != null) {
+ for (OrganizationURL organizationURL : descriptor.getOrganization().getURLs()) {
+ if (organizationURL != null && organizationURL.getURL() != null) {
+ idpMetadata.setOrganizationUrl(organizationURL.getURL().getLocalString());
+ break;
+ }
+ }
+ }
+ }
+ if (descriptor.getContactPersons() != null) {
+ for (ContactPerson person : descriptor.getContactPersons()) {
+ if (person == null || (person.getGivenName() == null && person.getSurName() == null)
+ || person.getEmailAddresses() == null) {
+ continue;
+ }
+ if (person.getGivenName() != null) {
+ idpMetadata.setContactPersonName(person.getGivenName().getName());
- EntityDescriptor idpEntityDescriptor = idpMetaDataProvider.getEntityDescriptor(this.identityProviderId);
+ } else if (person.getSurName() != null) {
+ idpMetadata.setContactPersonName(person.getSurName().getName());
+ }
+ for (EmailAddress emailAddress : person.getEmailAddresses()) {
+ if (emailAddress != null && emailAddress.getAddress() != null) {
+ idpMetadata.setContactPersonEmail(emailAddress.getAddress());
+ }
+ }
+ if (idpMetadata.getContactPersonName() != null && idpMetadata.getContactPersonEmail() != null) {
+ break;
+ }
+ }
+ }
- IDPSSODescriptor idpssoDescriptor = idpEntityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
- if (idpssoDescriptor != null) {
- for (SingleSignOnService ssos: idpssoDescriptor.getSingleSignOnServices()) {
+ IDPSSODescriptor idpDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
+ if (idpDescriptor != null) {
+ if (idpDescriptor.getSingleSignOnServices() != null) {
+ for (SingleSignOnService ssos : idpDescriptor.getSingleSignOnServices()) {
if (ssos.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
- this.idpSingleSignOnUrl = ssos.getLocation();
+ idpMetadata.setSsoUrl(ssos.getLocation());
}
}
-
- for (SingleLogoutService slos: idpssoDescriptor.getSingleLogoutServices()) {
+ }
+ if (idpDescriptor.getSingleLogoutServices() != null) {
+ for (SingleLogoutService slos : idpDescriptor.getSingleLogoutServices()) {
if (slos.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
- this.idpSingleLogOutUrl = slos.getLocation();
+ idpMetadata.setSloUrl(slos.getLocation());
}
}
+ }
- X509Certificate unspecifiedKey = null;
- for (KeyDescriptor kd: idpssoDescriptor.getKeyDescriptors()) {
+ X509Certificate unspecifiedKey = null;
+ if (idpDescriptor.getKeyDescriptors() != null) {
+ for (KeyDescriptor kd : idpDescriptor.getKeyDescriptors()) {
if (kd.getUse() == UsageType.SIGNING) {
try {
- this.idpSigningKey = KeyInfoHelper.getCertificates(kd.getKeyInfo()).get(0);
+ idpMetadata.setSigningCertificate(KeyInfoHelper.getCertificates(kd.getKeyInfo()).get(0));
} catch (CertificateException ignored) {
}
}
if (kd.getUse() == UsageType.ENCRYPTION) {
try {
- this.idpEncryptionKey = KeyInfoHelper.getCertificates(kd.getKeyInfo()).get(0);
+ idpMetadata.setEncryptionCertificate(KeyInfoHelper.getCertificates(kd.getKeyInfo()).get(0));
} catch (CertificateException ignored) {
}
}
@@ -201,15 +265,55 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
}
}
}
- if (idpSigningKey == null && unspecifiedKey != null) {
- idpSigningKey = unspecifiedKey;
+ }
+ if (idpMetadata.getSigningCertificate() == null && unspecifiedKey != null) {
+ idpMetadata.setSigningCertificate(unspecifiedKey);
+ }
+ if (idpMetadata.getEncryptionCertificate() == null && unspecifiedKey != null) {
+ idpMetadata.setEncryptionCertificate(unspecifiedKey);
+ }
+ if (idpMap.containsKey(idpMetadata.getEntityId())) {
+ s_logger.warn("Duplicate IdP metadata found with entity Id: " + idpMetadata.getEntityId());
+ }
+ idpMap.put(idpMetadata.getEntityId(), idpMetadata);
+ }
+ }
+
+ private void discoverAndAddIdp(XMLObject metadata, Map<String, SAMLProviderMetadata> idpMap) {
+ if (metadata instanceof EntityDescriptor) {
+ EntityDescriptor entityDescriptor = (EntityDescriptor) metadata;
+ addIdpToMap(entityDescriptor, idpMap);
+ } else if (metadata instanceof EntitiesDescriptor) {
+ EntitiesDescriptor entitiesDescriptor = (EntitiesDescriptor) metadata;
+ if (entitiesDescriptor.getEntityDescriptors() != null) {
+ for (EntityDescriptor entityDescriptor: entitiesDescriptor.getEntityDescriptors()) {
+ addIdpToMap(entityDescriptor, idpMap);
}
- if (idpEncryptionKey == null && unspecifiedKey != null) {
- idpEncryptionKey = unspecifiedKey;
+ }
+ if (entitiesDescriptor.getEntitiesDescriptors() != null) {
+ for (EntitiesDescriptor entitiesDescriptorInner: entitiesDescriptor.getEntitiesDescriptors()) {
+ discoverAndAddIdp(entitiesDescriptorInner, idpMap);
}
- } else {
- s_logger.warn("Provided IDP XML Metadata does not contain IDPSSODescriptor, SAML authentication may not work");
}
+ }
+ }
+
+ private boolean setup() {
+ if (!initSP()) {
+ s_logger.error("SAML Plugin failed to initialize, please fix the configuration and restart management server");
+ return false;
+ }
+ String idpMetaDataUrl = SAMLIdentityProviderMetadataURL.value();
+ int tolerance = 30000;
+ tolerance = SAMLTimeout.value();
+ try {
+ DefaultBootstrap.bootstrap();
+ idpMetaDataProvider = new HTTPMetadataProvider(idpMetaDataUrl, tolerance);
+ idpMetaDataProvider.setRequireValidMetadata(true);
+ idpMetaDataProvider.setParserPool(new BasicParserPool());
+ idpMetaDataProvider.initialize();
+ _idpMetadataMap.clear();
+ discoverAndAddIdp(idpMetaDataProvider.getMetadata(), _idpMetadataMap);
} catch (MetadataProviderException e) {
s_logger.error("Unable to read SAML2 IDP MetaData URL, error:" + e.getMessage());
s_logger.error("SAML2 Authentication may be unavailable");
@@ -219,11 +323,6 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
s_logger.error("Unable to setup SAML Auth Plugin due to NullPointerException" +
" please check the SAML IDP metadata URL and entity ID in global settings: " + e.getMessage());
}
-
- if (this.idpSingleLogOutUrl == null || this.idpSingleSignOnUrl == null) {
- s_logger.error("SAML based authentication won't work");
- }
-
return true;
}
@@ -236,54 +335,40 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
cmdList.add(SAML2LoginAPIAuthenticatorCmd.class);
cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class);
cmdList.add(GetServiceProviderMetaDataCmd.class);
+ cmdList.add(ListIdpsCmd.class);
return cmdList;
}
- public String getServiceProviderId() {
- return serviceProviderId;
- }
-
- public String getIdpSingleSignOnUrl() {
- return this.idpSingleSignOnUrl;
- }
-
- public String getIdpSingleLogOutUrl() {
- return this.idpSingleLogOutUrl;
- }
-
- public String getSpSingleSignOnUrl() {
- return spSingleSignOnUrl;
- }
-
- public String getSpSingleLogOutUrl() {
- return spSingleLogOutUrl;
- }
-
- public String getIdentityProviderId() {
- return identityProviderId;
+ @Override
+ public SAMLProviderMetadata getSPMetadata() {
+ return _spMetadata;
}
- public X509Certificate getIdpSigningKey() {
- return idpSigningKey;
+ @Override
+ public SAMLProviderMetadata getIdPMetadata(String entityId) {
+ if (entityId != null && _idpMetadataMap.containsKey(entityId)) {
+ return _idpMetadataMap.get(entityId);
+ }
+ String defaultIdpId = SAMLDefaultIdentityProviderId.value();
+ if (defaultIdpId != null && _idpMetadataMap.containsKey(defaultIdpId)) {
+ return _idpMetadataMap.get(defaultIdpId);
+ }
+ // In case of a single IdP, return that as default
+ if (_idpMetadataMap.size() == 1) {
+ return _idpMetadataMap.values().iterator().next();
+ }
+ return null;
}
- public X509Certificate getIdpEncryptionKey() {
- return idpEncryptionKey;
+ @Override
+ public Collection<SAMLProviderMetadata> getAllIdPMetadata() {
+ return _idpMetadataMap.values();
}
public Boolean isSAMLPluginEnabled() {
return SAMLIsPluginEnabled.value();
}
- public X509Certificate getSpX509Certificate() {
- return spX509Key;
- }
-
- @Override
- public KeyPair getSpKeyPair() {
- return spKeyPair;
- }
-
@Override
public String getConfigComponentName() {
return "SAML2-PLUGIN";
@@ -291,8 +376,14 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
@Override
public ConfigKey<?>[] getConfigKeys() {
- return new ConfigKey<?>[]{SAMLIsPluginEnabled, SAMLUserAttributeName, SAMLCloudStackRedirectionUrl,
+ return new ConfigKey<?>[] {
+ SAMLIsPluginEnabled, SAMLServiceProviderID,
+ SAMLServiceProviderContactPersonName, SAMLServiceProviderContactEmail,
+ SAMLServiceProviderOrgName, SAMLServiceProviderOrgUrl,
SAMLServiceProviderSingleSignOnURL, SAMLServiceProviderSingleLogOutURL,
- SAMLServiceProviderID, SAMLIdentityProviderMetadataURL, SAMLIdentityProviderId, SAMLTimeout};
+ SAMLCloudStackRedirectionUrl, SAMLUserAttributeName,
+ SAMLDefaultDomain,
+ SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId,
+ SAMLTimeout};
}
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
index de2481e..92a17a9 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
@@ -63,8 +63,8 @@ public class SAML2UserAuthenticator extends DefaultUserAuthenticator {
return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
} else {
User user = _userDao.getUser(userAccount.getId());
- if (user != null && requestParameters != null && requestParameters.containsKey(SAMLUtils.SAML_RESPONSE)) {
- final String samlResponse = ((String[])requestParameters.get(SAMLUtils.SAML_RESPONSE))[0];
+ if (user != null && requestParameters != null && requestParameters.containsKey(SAMLPluginConstants.SAML_RESPONSE)) {
+ final String samlResponse = ((String[])requestParameters.get(SAMLPluginConstants.SAML_RESPONSE))[0];
Response responseObject = null;
try {
DefaultBootstrap.bootstrap();
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLPluginConstants.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLPluginConstants.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLPluginConstants.java
new file mode 100644
index 0000000..4745915
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLPluginConstants.java
@@ -0,0 +1,27 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package org.apache.cloudstack.saml;
+
+public class SAMLPluginConstants {
+ public static final String SAML_RESPONSE = "SAMLResponse";
+ public static final String SAML_IDPID = "SAML_IDPID";
+ public static final String SAML_SESSION = "SAML_SESSION";
+ public static final String SAMLSP_KEYPAIR = "SAMLSP_KEYPAIR";
+ public static final String SAMLSP_X509CERT = "SAMLSP_X509CERT";
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLProviderMetadata.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLProviderMetadata.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLProviderMetadata.java
new file mode 100644
index 0000000..c7138a1
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLProviderMetadata.java
@@ -0,0 +1,122 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.saml;
+
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+
+public class SAMLProviderMetadata {
+ private String entityId;
+ private String organizationName;
+ private String organizationUrl;
+ private String contactPersonName;
+ private String contactPersonEmail;
+ private String ssoUrl;
+ private String sloUrl;
+ private KeyPair keyPair;
+ private X509Certificate signingCertificate;
+ private X509Certificate encryptionCertificate;
+
+ public SAMLProviderMetadata() {
+ }
+
+ public void setCommonCertificate(X509Certificate certificate) {
+ this.signingCertificate = certificate;
+ this.encryptionCertificate = certificate;
+ }
+
+ public String getEntityId() {
+ return entityId;
+ }
+
+ public void setEntityId(String entityId) {
+ this.entityId = entityId;
+ }
+
+ public String getContactPersonName() {
+ return contactPersonName;
+ }
+
+ public void setContactPersonName(String contactPersonName) {
+ this.contactPersonName = contactPersonName;
+ }
+
+ public String getContactPersonEmail() {
+ return contactPersonEmail;
+ }
+
+ public void setContactPersonEmail(String contactPersonEmail) {
+ this.contactPersonEmail = contactPersonEmail;
+ }
+
+ public String getOrganizationName() {
+ return organizationName;
+ }
+
+ public void setOrganizationName(String organizationName) {
+ this.organizationName = organizationName;
+ }
+
+ public String getOrganizationUrl() {
+ return organizationUrl;
+ }
+
+ public void setOrganizationUrl(String organizationUrl) {
+ this.organizationUrl = organizationUrl;
+ }
+
+ public KeyPair getKeyPair() {
+ return keyPair;
+ }
+
+ public void setKeyPair(KeyPair keyPair) {
+ this.keyPair = keyPair;
+ }
+
+ public X509Certificate getSigningCertificate() {
+ return signingCertificate;
+ }
+
+ public void setSigningCertificate(X509Certificate signingCertificate) {
+ this.signingCertificate = signingCertificate;
+ }
+
+ public X509Certificate getEncryptionCertificate() {
+ return encryptionCertificate;
+ }
+
+ public void setEncryptionCertificate(X509Certificate encryptionCertificate) {
+ this.encryptionCertificate = encryptionCertificate;
+ }
+
+ public String getSsoUrl() {
+ return ssoUrl;
+ }
+
+ public void setSsoUrl(String ssoUrl) {
+ this.ssoUrl = ssoUrl;
+ }
+
+ public String getSloUrl() {
+ return sloUrl;
+ }
+
+ public void setSloUrl(String sloUrl) {
+ this.sloUrl = sloUrl;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
index 95cbefc..e2a9dbc 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
@@ -26,6 +26,7 @@ import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.opensaml.Configuration;
+import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLVersion;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.Assertion;
@@ -37,17 +38,13 @@ import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.LogoutRequest;
-import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml2.core.Response;
-import org.opensaml.saml2.core.SessionIndex;
import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml2.core.impl.LogoutRequestBuilder;
-import org.opensaml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
-import org.opensaml.saml2.core.impl.SessionIndexBuilder;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.Marshaller;
@@ -66,6 +63,7 @@ import javax.security.auth.x500.X500Principal;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.FactoryConfigurationError;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -97,12 +95,6 @@ import java.util.zip.DeflaterOutputStream;
public class SAMLUtils {
public static final Logger s_logger = Logger.getLogger(SAMLUtils.class);
- public static final String SAML_RESPONSE = "SAMLResponse";
- public static final String SAML_NAMEID = "SAML_NAMEID";
- public static final String SAML_SESSION = "SAML_SESSION";
- public static final String SAMLSP_KEYPAIR = "SAMLSP_KEYPAIR";
- public static final String SAMLSP_X509CERT = "SAMLSP_X509CERT";
-
public static String generateSecureRandomId() {
return new BigInteger(160, new SecureRandom()).toString(32);
}
@@ -141,6 +133,22 @@ public class SAMLUtils {
return null;
}
+ public static String buildAuthnRequestUrl(SAMLProviderMetadata spMetadata, SAMLProviderMetadata idpMetadata) {
+ String redirectUrl = "";
+ try {
+ DefaultBootstrap.bootstrap();
+ AuthnRequest authnRequest = SAMLUtils.buildAuthnRequestObject(spMetadata.getEntityId(), idpMetadata.getSsoUrl(), spMetadata.getSsoUrl());
+ PrivateKey privateKey = null;
+ if (spMetadata.getKeyPair() != null) {
+ privateKey = spMetadata.getKeyPair().getPrivate();
+ }
+ redirectUrl = idpMetadata.getSsoUrl() + "?" + SAMLUtils.generateSAMLRequestSignature("SAMLRequest=" + SAMLUtils.encodeSAMLRequest(authnRequest), privateKey);
+ } catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException | NoSuchAlgorithmException | InvalidKeyException | java.security.SignatureException e) {
+ s_logger.error("SAML AuthnRequest message building error: " + e.getMessage());
+ }
+ return redirectUrl;
+ }
+
public static AuthnRequest buildAuthnRequestObject(String spId, String idpUrl, String consumerUrl) {
String authnId = generateSecureRandomId();
// Issuer object
@@ -179,26 +187,17 @@ public class SAMLUtils {
return authnRequest;
}
- public static LogoutRequest buildLogoutRequest(String logoutUrl, String spId, NameID sessionNameId, String sessionIndex) {
+ public static LogoutRequest buildLogoutRequest(String logoutUrl, String spId) {
IssuerBuilder issuerBuilder = new IssuerBuilder();
Issuer issuer = issuerBuilder.buildObject();
issuer.setValue(spId);
- SessionIndex sessionIndexElement = new SessionIndexBuilder().buildObject();
- sessionIndexElement.setSessionIndex(sessionIndex);
-
- NameID nameID = new NameIDBuilder().buildObject();
- nameID.setValue(sessionNameId.getValue());
- nameID.setFormat(sessionNameId.getFormat());
-
LogoutRequest logoutRequest = new LogoutRequestBuilder().buildObject();
logoutRequest.setID(generateSecureRandomId());
logoutRequest.setDestination(logoutUrl);
logoutRequest.setVersion(SAMLVersion.VERSION_20);
logoutRequest.setIssueInstant(new DateTime());
logoutRequest.setIssuer(issuer);
- logoutRequest.getSessionIndexes().add(sessionIndexElement);
- logoutRequest.setNameID(nameID);
return logoutRequest;
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
index e6446a1..32d0fba 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
@@ -29,7 +29,6 @@ import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.http.HttpServletRequest;
@@ -77,19 +76,9 @@ public class GetServiceProviderMetaDataCmdTest {
String spId = "someSPID";
String url = "someUrl";
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
- Mockito.when(samlAuthManager.getServiceProviderId()).thenReturn(spId);
- Mockito.when(samlAuthManager.getIdpSigningKey()).thenReturn(cert);
- Mockito.when(samlAuthManager.getIdpSingleLogOutUrl()).thenReturn(url);
- Mockito.when(samlAuthManager.getSpSingleLogOutUrl()).thenReturn(url);
String result = cmd.authenticate("command", null, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
Assert.assertTrue(result.contains("md:EntityDescriptor"));
-
- Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getServiceProviderId();
- Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getSpSingleSignOnUrl();
- Mockito.verify(samlAuthManager, Mockito.atLeast(1)).getSpSingleLogOutUrl();
- Mockito.verify(samlAuthManager, Mockito.never()).getIdpSingleSignOnUrl();
- Mockito.verify(samlAuthManager, Mockito.never()).getIdpSingleLogOutUrl();
}
@Test
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java
index 239bdcc..5b37388 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAML2UserAuthenticatorTest.java
@@ -25,8 +25,8 @@ import com.cloud.user.UserVO;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.utils.Pair;
+import org.apache.cloudstack.saml.SAMLPluginConstants;
import org.apache.cloudstack.saml.SAML2UserAuthenticator;
-import org.apache.cloudstack.saml.SAMLUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -79,7 +79,7 @@ public class SAML2UserAuthenticatorTest {
Assert.assertFalse(pair.first());
// When there is SAMLRequest in params and user is same as the mocked one
- params.put(SAMLUtils.SAML_RESPONSE, new String[]{"RandomString"});
+ params.put(SAMLPluginConstants.SAML_RESPONSE, new String[]{"RandomString"});
pair = authenticator.authenticate("someUID", "random", 1l, params);
Assert.assertFalse(pair.first());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
index cef5c84..20def87 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
@@ -24,8 +24,6 @@ import org.apache.cloudstack.saml.SAMLUtils;
import org.junit.Test;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.LogoutRequest;
-import org.opensaml.saml2.core.NameID;
-import org.opensaml.saml2.core.impl.NameIDBuilder;
import java.security.KeyPair;
import java.security.PrivateKey;
@@ -54,13 +52,9 @@ public class SAMLUtilsTest extends TestCase {
String logoutUrl = "http://logoutUrl";
String spId = "cloudstack";
String sessionIndex = "12345";
- String nameIdString = "someNameID";
- NameID sessionNameId = new NameIDBuilder().buildObject();
- sessionNameId.setValue(nameIdString);
- LogoutRequest req = SAMLUtils.buildLogoutRequest(logoutUrl, spId, sessionNameId, sessionIndex);
+ LogoutRequest req = SAMLUtils.buildLogoutRequest(logoutUrl, spId);
assertEquals(req.getDestination(), logoutUrl);
assertEquals(req.getIssuer().getValue(), spId);
- assertEquals(req.getNameID().getValue(), nameIdString);
assertEquals(req.getSessionIndexes().get(0).getSessionIndex(), sessionIndex);
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
index 9170109..c2d4960 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
@@ -30,6 +30,7 @@ import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.auth.APIAuthenticationType;
import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.cloudstack.saml.SAMLPluginConstants;
import org.apache.cloudstack.saml.SAMLUtils;
import org.joda.time.DateTime;
import org.junit.Assert;
@@ -148,10 +149,6 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
String spId = "someSPID";
String url = "someUrl";
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
- Mockito.when(samlAuthManager.getServiceProviderId()).thenReturn(spId);
- Mockito.when(samlAuthManager.getIdpSigningKey()).thenReturn(null);
- Mockito.when(samlAuthManager.getIdpSingleSignOnUrl()).thenReturn(url);
- Mockito.when(samlAuthManager.getSpSingleSignOnUrl()).thenReturn(url);
Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null);
@@ -169,7 +166,7 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
Mockito.verify(resp, Mockito.times(1)).sendRedirect(Mockito.anyString());
// SSO SAMLResponse verification test, this should throw ServerApiException for auth failure
- params.put(SAMLUtils.SAML_RESPONSE, new String[]{"Some String"});
+ params.put(SAMLPluginConstants.SAML_RESPONSE, new String[]{"Some String"});
Mockito.stub(cmd.processSAMLResponse(Mockito.anyString())).toReturn(buildMockResponse());
try {
cmd.authenticate("command", params, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
index 8863e50..eff4b29 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
@@ -22,7 +22,6 @@ package org.apache.cloudstack.api.command;
import com.cloud.utils.HttpUtils;
import org.apache.cloudstack.api.ApiServerService;
import org.apache.cloudstack.api.auth.APIAuthenticationType;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.saml.SAML2AuthManager;
import org.apache.cloudstack.saml.SAMLUtils;
import org.junit.Assert;
@@ -48,9 +47,6 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
SAML2AuthManager samlAuthManager;
@Mock
- ConfigurationDao configDao;
-
- @Mock
HttpSession session;
@Mock
@@ -71,19 +67,10 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
managerField.setAccessible(true);
managerField.set(cmd, samlAuthManager);
- Field configDaoField = SAML2LogoutAPIAuthenticatorCmd.class.getDeclaredField("_configDao");
- configDaoField.setAccessible(true);
- configDaoField.set(cmd, configDao);
-
String spId = "someSPID";
String url = "someUrl";
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
- Mockito.when(samlAuthManager.getServiceProviderId()).thenReturn(spId);
- Mockito.when(samlAuthManager.getIdpSigningKey()).thenReturn(cert);
- Mockito.when(samlAuthManager.getIdpSingleLogOutUrl()).thenReturn(url);
- Mockito.when(samlAuthManager.getSpSingleLogOutUrl()).thenReturn(url);
Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null);
- Mockito.when(configDao.getValue(Mockito.anyString())).thenReturn("someString");
cmd.authenticate("command", null, session, "random", HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
Mockito.verify(resp, Mockito.times(1)).sendRedirect(Mockito.anyString());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b8b21aa9/ui/css/cloudstack3.css
----------------------------------------------------------------------
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 0a2c57f..8e04ad0 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -369,7 +369,7 @@ body.login {
.login .select-language select {
width: 260px;
border: 1px solid #808080;
- margin-top: 30px;
+ margin-top: 20px;
/*+border-radius:4px;*/
-moz-border-radius: 4px;
-webkit-border-radius: 4px;