saml: Have the plugin use IDP metadata from URL, get values from Config Signed-off-by: Rohit Yadav <rohit.ya...@shapeblue.com>
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/06e90992 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/06e90992 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/06e90992 Branch: refs/heads/master Commit: 06e909923a604a348c9ff18380a868b96145c6e2 Parents: 1b0f81e Author: Rohit Yadav <rohit.ya...@shapeblue.com> Authored: Sun Aug 24 17:34:20 2014 +0200 Committer: Rohit Yadav <rohit.ya...@shapeblue.com> Committed: Thu Aug 28 19:45:24 2014 +0200 ---------------------------------------------------------------------- .../cloudstack/saml2/spring-saml2-context.xml | 2 +- .../command/SAML2LoginAPIAuthenticatorCmd.java | 28 +++- .../cloudstack/saml/SAML2AuthManagerImpl.java | 131 +++++++++++++++++++ .../cloudstack/saml/SAML2AuthServiceImpl.java | 51 -------- 4 files changed, 153 insertions(+), 59 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/06e90992/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml b/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml index 15e085d..92f89b8 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 @@ <property name="name" value="SAML2"/> </bean> - <bean id="SAML2Manager" class="org.apache.cloudstack.saml.SAML2AuthServiceImpl"> + <bean id="SAML2Manager" class="org.apache.cloudstack.saml.SAML2AuthManagerImpl"> <property name="name" value="SAML2Auth"/> </bean> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/06e90992/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 463df7d..ec3a4d2 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<String, Object[]> 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 { http://git-wip-us.apache.org/repos/asf/cloudstack/blob/06e90992/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 new file mode 100644 index 0000000..41595b6 --- /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<Class<?>> getAuthCommands() { + List<Class<?>> cmdList = new ArrayList<Class<?>>(); + 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; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/06e90992/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthServiceImpl.java ---------------------------------------------------------------------- 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 44e29ca..0000000 --- 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<Class<?>> getAuthCommands() { - List<Class<?>> cmdList = new ArrayList<Class<?>>(); - cmdList.add(SAML2LoginAPIAuthenticatorCmd.class); - cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class); - return cmdList; - } -}