diff --git a/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml b/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
index 15e085ded20..92f89b8dfbc 100644
--- a/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
+++ b/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
@@ -29,7 +29,7 @@
-
+
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 463df7d117e..ec3a4d242bb 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
@@ -17,7 +17,6 @@
package org.apache.cloudstack.api.command;
-import org.apache.cloudstack.api.ApiServerService;
import com.cloud.api.response.ApiResponseSerializer;
import com.cloud.exception.CloudAuthenticationException;
import com.cloud.user.Account;
@@ -27,6 +26,7 @@ import com.cloud.utils.db.EntityManager;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.ApiServerService;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
@@ -34,6 +34,7 @@ import org.apache.cloudstack.api.auth.APIAuthenticationType;
import org.apache.cloudstack.api.auth.APIAuthenticator;
import org.apache.cloudstack.api.response.LoginCmdResponse;
import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.saml.SAML2AuthManager;
import org.apache.cloudstack.utils.auth.SAMLUtils;
import org.apache.log4j.Logger;
import org.opensaml.DefaultBootstrap;
@@ -79,6 +80,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
ApiServerService _apiServer;
@Inject
EntityManager _entityMgr;
+ @Inject
+ SAML2AuthManager _samlAuthManager;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
@@ -108,13 +111,20 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
}
- public String buildAuthnRequestUrl(String consumerUrl, String identityProviderUrl) {
- String randomId = new BigInteger(130, new SecureRandom()).toString(32);
- String spId = "org.apache.cloudstack";
+ public String buildAuthnRequestUrl(String idpUrl) {
+ String randomSecureId = new BigInteger(130, new SecureRandom()).toString(32);
+ 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(randomId, spId, identityProviderUrl, consumerUrl);
+ AuthnRequest authnRequest = SAMLUtils.buildAuthnRequestObject(randomSecureId, spId, identityProviderUrl, consumerUrl);
redirectUrl = identityProviderUrl + "?SAMLRequest=" + SAMLUtils.encodeSAMLRequest(authnRequest);
} catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException e) {
s_logger.error("SAML AuthnRequest message building error: " + e.getMessage());
@@ -137,8 +147,12 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
public String authenticate(final String command, final Map params, final HttpSession session, final String remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
try {
if (!params.containsKey("SAMLResponse")) {
- final String[] idps = (String[])params.get("idpurl");
- String redirectUrl = buildAuthnRequestUrl("http://localhost:8080/client/api?command=samlsso", idps[0]);
+ String idpUrl = null;
+ final String[] idps = (String[])params.get(ApiConstants.IDP_URL);
+ if (idps != null && idps.length > 0) {
+ idpUrl = idps[0];
+ }
+ String redirectUrl = buildAuthnRequestUrl(idpUrl);
resp.sendRedirect(redirectUrl);
return "";
} else {
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
new file mode 100644
index 00000000000..41595b6108a
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
@@ -0,0 +1,131 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.saml;
+
+import com.cloud.configuration.Config;
+import com.cloud.utils.component.AdapterBase;
+import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.api.command.SAML2LoginAPIAuthenticatorCmd;
+import org.apache.cloudstack.api.command.SAML2LogoutAPIAuthenticatorCmd;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.log4j.Logger;
+import org.opensaml.common.xml.SAMLConstants;
+import org.opensaml.saml2.metadata.EntityDescriptor;
+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.parse.BasicParserPool;
+import org.springframework.stereotype.Component;
+
+import javax.ejb.Local;
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+@Local(value = {PluggableAPIAuthenticator.class, SAML2AuthManager.class})
+public class SAML2AuthManagerImpl extends AdapterBase implements PluggableAPIAuthenticator, SAML2AuthManager {
+ private static final Logger s_logger = Logger.getLogger(SAML2AuthManagerImpl.class);
+
+ private String serviceProviderId;
+ private String spSingleSignOnUrl;
+ private String spSingleLogOutUrl;
+
+ private String idpSingleSignOnUrl;
+ private String idpSingleLogOutUrl;
+
+ @Inject
+ ConfigurationDao _configDao;
+
+ protected SAML2AuthManagerImpl() {
+ super();
+ }
+
+ @Override
+ public boolean start() {
+ this.serviceProviderId = _configDao.getValue(Config.SAMLServiceProviderID.key());
+ this.spSingleSignOnUrl = _configDao.getValue(Config.SAMLServiceProviderSingleSignOnURL.key());
+ this.spSingleLogOutUrl = _configDao.getValue(Config.SAMLServiceProviderSingleLogOutURL.key());
+
+ String idpMetaDataUrl = _configDao.getValue(Config.SAMLIdentityProviderMetadataURL.key());
+
+ int tolerance = 30000;
+ String timeout = _configDao.getValue(Config.SAMLTimeout.key());
+ if (timeout != null) {
+ tolerance = Integer.parseInt(timeout);
+ }
+
+ try {
+ HTTPMetadataProvider idpMetaDataProvider = new HTTPMetadataProvider(idpMetaDataUrl, tolerance);
+
+ idpMetaDataProvider.setRequireValidMetadata(true);
+ idpMetaDataProvider.setParserPool(new BasicParserPool());
+ idpMetaDataProvider.initialize();
+
+ EntityDescriptor idpEntityDescriptor = idpMetaDataProvider.getEntityDescriptor("Some entity id");
+ for (SingleSignOnService ssos: idpEntityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleSignOnServices()) {
+ if (ssos.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
+ this.idpSingleSignOnUrl = ssos.getLocation();
+ }
+ }
+ for (SingleLogoutService slos: idpEntityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleLogoutServices()) {
+ if (slos.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
+ this.idpSingleLogOutUrl = slos.getLocation();
+ }
+ }
+
+ } catch (MetadataProviderException e) {
+ s_logger.error("Unable to read SAML2 IDP MetaData URL, error:" + e.getMessage());
+ s_logger.error("SAML2 Authentication may be unavailable");
+ }
+
+ if (this.idpSingleLogOutUrl == null || this.idpSingleSignOnUrl == null) {
+ s_logger.error("The current IDP does not support HTTP redirected authentication, SAML based authentication cannot work with this IDP");
+ }
+
+ return true;
+ }
+
+ @Override
+ public List> getAuthCommands() {
+ List> cmdList = new ArrayList>();
+ cmdList.add(SAML2LoginAPIAuthenticatorCmd.class);
+ cmdList.add(SAML2LogoutAPIAuthenticatorCmd.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;
+ }
+}
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthServiceImpl.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthServiceImpl.java
deleted file mode 100644
index 44e29ca7ac6..00000000000
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthServiceImpl.java
+++ /dev/null
@@ -1,51 +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.saml;
-
-import com.cloud.utils.component.AdapterBase;
-import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
-import org.apache.cloudstack.api.command.SAML2LoginAPIAuthenticatorCmd;
-import org.apache.cloudstack.api.command.SAML2LogoutAPIAuthenticatorCmd;
-import org.apache.log4j.Logger;
-import org.springframework.stereotype.Component;
-
-import javax.ejb.Local;
-import java.util.ArrayList;
-import java.util.List;
-
-@Component
-@Local(value = PluggableAPIAuthenticator.class)
-public class SAML2AuthServiceImpl extends AdapterBase implements PluggableAPIAuthenticator {
- private static final Logger s_logger = Logger.getLogger(SAML2AuthServiceImpl.class);
-
- protected SAML2AuthServiceImpl() {
- super();
- }
-
- @Override
- public boolean start() {
- return true;
- }
-
- @Override
- public List> getAuthCommands() {
- List> cmdList = new ArrayList>();
- cmdList.add(SAML2LoginAPIAuthenticatorCmd.class);
- cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class);
- return cmdList;
- }
-}