Return-Path: X-Original-To: apmail-cloudstack-commits-archive@www.apache.org Delivered-To: apmail-cloudstack-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B1DD21824B for ; Mon, 3 Aug 2015 19:46:27 +0000 (UTC) Received: (qmail 80129 invoked by uid 500); 3 Aug 2015 19:46:27 -0000 Delivered-To: apmail-cloudstack-commits-archive@cloudstack.apache.org Received: (qmail 80021 invoked by uid 500); 3 Aug 2015 19:46:27 -0000 Mailing-List: contact commits-help@cloudstack.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cloudstack.apache.org Delivered-To: mailing list commits@cloudstack.apache.org Received: (qmail 79868 invoked by uid 99); 3 Aug 2015 19:46:27 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 03 Aug 2015 19:46:27 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 48F79E09DE; Mon, 3 Aug 2015 19:46:27 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: bhaisaab@apache.org To: commits@cloudstack.apache.org Date: Mon, 03 Aug 2015 19:46:29 -0000 Message-Id: In-Reply-To: <14ecbe80ade14692a3e19e708c4e507d@git.apache.org> References: <14ecbe80ade14692a3e19e708c4e507d@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [3/3] git commit: updated refs/heads/4.5-samlfixes to b6782a0 CLOUDSTACK-8701: Allow SAML users to switch accounts SAML authorized accounts might be across various domains, this allows for switching of accounts only in case of SAML authenticated user accounts across other accounts with the same SAML uid/username. Signed-off-by: Rohit Yadav Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/b6782a05 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/b6782a05 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/b6782a05 Branch: refs/heads/4.5-samlfixes Commit: b6782a05fa34287a10551828f0f129a8f866703b Parents: c067b51 Author: Rohit Yadav Authored: Mon Aug 3 12:36:14 2015 +0530 Committer: Rohit Yadav Committed: Tue Aug 4 01:16:09 2015 +0530 ---------------------------------------------------------------------- client/tomcatconf/commands.properties.in | 1 + .../command/GetServiceProviderMetaDataCmd.java | 2 +- .../command/ListAndSwitchSAMLAccountCmd.java | 195 +++++++++++++++++++ .../cloudstack/api/command/ListIdpsCmd.java | 2 +- .../command/SAML2LoginAPIAuthenticatorCmd.java | 36 +--- .../api/response/SamlUserAccountResponse.java | 99 ++++++++++ .../cloudstack/saml/SAML2AuthManagerImpl.java | 2 + .../cloudstack/saml/SAML2UserAuthenticator.java | 22 +-- .../org/apache/cloudstack/saml/SAMLUtils.java | 18 ++ ui/index.jsp | 7 - ui/scripts/ui-custom/login.js | 8 +- ui/scripts/ui/core.js | 77 ++++++++ 12 files changed, 401 insertions(+), 68 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/client/tomcatconf/commands.properties.in ---------------------------------------------------------------------- diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index a66a3dc..a69605c 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -29,6 +29,7 @@ getSPMetadata=15 listIdps=15 authorizeSamlSso=7 listSamlAuthorization=7 +listAndSwitchSamlAccount=15 ### Account commands createAccount=7 http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/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 ffaad7a..bed594f 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 @@ -266,7 +266,7 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent } } if (_samlAuthManager == null) { - s_logger.error("No suitable Pluggable Authentication Manager found for SAML2 Login Cmd"); + s_logger.error("No suitable Pluggable Authentication Manager found for SAML2 getSPMetadata Cmd"); } } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListAndSwitchSAMLAccountCmd.java ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListAndSwitchSAMLAccountCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListAndSwitchSAMLAccountCmd.java new file mode 100644 index 0000000..db0d6dc --- /dev/null +++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListAndSwitchSAMLAccountCmd.java @@ -0,0 +1,195 @@ +// 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.domain.Domain; +import com.cloud.domain.dao.DomainDao; +import com.cloud.user.Account; +import com.cloud.user.User; +import com.cloud.user.UserAccount; +import com.cloud.user.UserAccountVO; +import com.cloud.user.dao.UserAccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.HttpUtils; +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; +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.DomainResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.LoginCmdResponse; +import org.apache.cloudstack.api.response.SamlUserAccountResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.saml.SAML2AuthManager; +import org.apache.cloudstack.saml.SAMLUtils; +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 = "listAndSwitchSamlAccount", description = "Lists and switches to other SAML accounts owned by the SAML user", responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthenticator { + public static final Logger s_logger = Logger.getLogger(ListAndSwitchSAMLAccountCmd.class.getName()); + private static final String s_name = "listandswitchsamlaccountresponse"; + + @Inject + ApiServerService _apiServer; + + @Inject + private UserAccountDao _userAccountDao; + @Inject + private UserDao _userDao; + @Inject + private DomainDao _domainDao; + + SAML2AuthManager _samlAuthManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, required = false, description = "User uuid") + private Long userId; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, required = false, description = "Domain uuid") + private Long domainId; + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication plugin api, cannot be used directly"); + } + + @Override + public String authenticate(final String command, final Map params, final HttpSession session, final String remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException { + if (session == null || session.isNew()) { + throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, _apiServer.getSerializedApiError(ApiErrorCode.UNAUTHORIZED.getHttpCode(), + "Only authenticated saml users can request this API", + params, responseType)); + } + + if (!HttpUtils.validateSessionKey(session, params, req.getCookies(), ApiConstants.SESSIONKEY)) { + throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, _apiServer.getSerializedApiError(ApiErrorCode.UNAUTHORIZED.getHttpCode(), + "Unauthorized session, please re-login", + params, responseType)); + } + + final long currentUserId = (Long) session.getAttribute("userid"); + final UserAccount currentUserAccount = _accountService.getUserAccountById(currentUserId); + if (currentUserAccount == null || currentUserAccount.getSource() != User.Source.SAML2) { + throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), + "Only authenticated saml users can request this API", + params, responseType)); + } + + String userUuid = null; + String domainUuid = null; + if (params.containsKey(ApiConstants.USER_ID)) { + userUuid = ((String[])params.get(ApiConstants.USER_ID))[0]; + } + if (params.containsKey(ApiConstants.DOMAIN_ID)) { + domainUuid = ((String[])params.get(ApiConstants.DOMAIN_ID))[0]; + } + + if (userUuid != null && domainUuid != null) { + final User user = _userDao.findByUuid(userUuid); + final Domain domain = _domainDao.findByUuid(domainUuid); + final UserAccount nextUserAccount = _accountService.getUserAccountById(user.getId()); + if (!nextUserAccount.getUsername().equals(currentUserAccount.getUsername()) + || !nextUserAccount.getExternalEntity().equals(currentUserAccount.getExternalEntity()) + || (nextUserAccount.getDomainId() != domain.getId()) + || (nextUserAccount.getSource() != User.Source.SAML2)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(), + "User account is not allowed to switch to the requested account", + params, responseType)); + } + try { + if (_apiServer.verifyUser(nextUserAccount.getId())) { + final LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, nextUserAccount.getUsername(), nextUserAccount.getUsername() + nextUserAccount.getSource().toString(), + nextUserAccount.getDomainId(), null, remoteAddress, params); + SAMLUtils.setupSamlUserCookies(loginResponse, resp); + resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value()); + return ApiResponseSerializer.toSerializedString(loginResponse, responseType); + } + } catch (final Exception ignored) { + } + } else { + List switchableAccounts = _userAccountDao.getAllUsersByNameAndEntity(currentUserAccount.getUsername(), currentUserAccount.getExternalEntity()); + if (switchableAccounts != null && switchableAccounts.size() > 0 && currentUserId != User.UID_SYSTEM) { + List accountResponses = new ArrayList(); + for (UserAccountVO userAccount: switchableAccounts) { + User user = _userDao.getUser(userAccount.getId()); + Domain domain = _domainService.getDomain(userAccount.getDomainId()); + SamlUserAccountResponse accountResponse = new SamlUserAccountResponse(); + accountResponse.setUserId(user.getUuid()); + accountResponse.setUserName(user.getUsername()); + accountResponse.setDomainId(domain.getUuid()); + accountResponse.setDomainName(domain.getName()); + accountResponse.setAccountName(userAccount.getAccountName()); + accountResponse.setIdpId(user.getExternalEntity()); + accountResponses.add(accountResponse); + } + ListResponse response = new ListResponse(); + response.setResponses(accountResponses); + response.setResponseName(getCommandName()); + return ApiResponseSerializer.toSerializedString(response, responseType); + } + } + throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), + "Unable to switch to requested SAML account. Please make sure your user/account is enabled. Please contact your administrator.", + params, responseType)); + } + + @Override + public APIAuthenticationType getAPIType() { + return APIAuthenticationType.READONLY_API; + } + + @Override + public void setAuthenticators(List 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 listAndSwitchSamlAccount Cmd"); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/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 index 43ecfc5..4771342 100644 --- 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 @@ -111,4 +111,4 @@ public class ListIdpsCmd extends BaseCmd implements APIAuthenticator { 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/b6782a05/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 d4940ec..188eb6b 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,13 +17,11 @@ package org.apache.cloudstack.api.command; import com.cloud.api.response.ApiResponseSerializer; -import com.cloud.exception.CloudAuthenticationException; import com.cloud.user.Account; import com.cloud.user.DomainManager; import com.cloud.user.UserAccount; import com.cloud.user.UserAccountVO; import com.cloud.user.dao.UserAccountDao; -import com.cloud.utils.HttpUtils; import com.cloud.utils.db.EntityManager; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -64,14 +62,12 @@ import org.opensaml.xml.validation.ValidationException; import org.xml.sax.SAXException; import javax.inject.Inject; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.FactoryConfigurationError; import java.io.IOException; -import java.net.URLEncoder; import java.util.List; import java.util.Map; @@ -195,7 +191,6 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent } String username = null; - Long domainId = null; Issuer issuer = processedSAMLResponse.getIssuer(); SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata(); SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(issuer.getValue()); @@ -204,9 +199,6 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent s_logger.debug("Received SAMLResponse in response to id=" + responseToId); SAMLTokenVO token = _samlAuthManager.getToken(responseToId); if (token != null) { - if (token.getDomainId() != null) { - domainId = token.getDomainId(); - } if (!(token.getEntity().equalsIgnoreCase(issuer.getValue()))) { throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), "The SAML response contains Issuer Entity ID that is different from the original SAML request", @@ -298,17 +290,9 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent UserAccount userAccount = null; List possibleUserAccounts = _userAccountDao.getAllUsersByNameAndEntity(username, issuer.getValue()); if (possibleUserAccounts != null && possibleUserAccounts.size() > 0) { - if (possibleUserAccounts.size() == 1) { - userAccount = possibleUserAccounts.get(0); - } else if (possibleUserAccounts.size() > 1) { - if (domainId != null) { - userAccount = _userAccountDao.getUserAccount(username, domainId); - } else { - throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), - "You have accounts in multiple domains, please re-login by specifying the domain you want to log into.", - params, responseType)); - } - } + // By default, log into the first user account + // Users can switch to other allowed accounts later + userAccount = possibleUserAccounts.get(0); } if (userAccount == null || userAccount.getExternalEntity() == null || !_samlAuthManager.isUserAuthorized(userAccount.getId(), issuer.getValue())) { @@ -322,21 +306,11 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent if (_apiServer.verifyUser(userAccount.getId())) { LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, userAccount.getUsername(), userAccount.getUsername() + userAccount.getSource().toString(), userAccount.getDomainId(), null, remoteAddress, params); - resp.addCookie(new Cookie("userid", URLEncoder.encode(loginResponse.getUserId(), HttpUtils.UTF_8))); - resp.addCookie(new Cookie("domainid", URLEncoder.encode(loginResponse.getDomainId(), HttpUtils.UTF_8))); - resp.addCookie(new Cookie("role", URLEncoder.encode(loginResponse.getType(), HttpUtils.UTF_8))); - resp.addCookie(new Cookie("username", URLEncoder.encode(loginResponse.getUsername(), HttpUtils.UTF_8))); - resp.addCookie(new Cookie("account", URLEncoder.encode(loginResponse.getAccount(), HttpUtils.UTF_8))); - String timezone = loginResponse.getTimeZone(); - if (timezone != null) { - resp.addCookie(new Cookie("timezone", URLEncoder.encode(timezone, HttpUtils.UTF_8))); - } - resp.addCookie(new Cookie("userfullname", URLEncoder.encode(loginResponse.getFirstName() + " " + loginResponse.getLastName(), HttpUtils.UTF_8).replace("+", "%20"))); - resp.addHeader("SET-COOKIE", String.format("%s=%s;HttpOnly", ApiConstants.SESSIONKEY, loginResponse.getSessionKey())); + SAMLUtils.setupSamlUserCookies(loginResponse, resp); resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value()); return ApiResponseSerializer.toSerializedString(loginResponse, responseType); } - } catch (final CloudAuthenticationException ignored) { + } catch (final Exception ignored) { } } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SamlUserAccountResponse.java ---------------------------------------------------------------------- diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SamlUserAccountResponse.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SamlUserAccountResponse.java new file mode 100644 index 0000000..f0927e3 --- /dev/null +++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SamlUserAccountResponse.java @@ -0,0 +1,99 @@ +// 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 SamlUserAccountResponse extends AuthenticationCmdResponse { + @SerializedName("userId") + @Param(description = "The User Id") + private String userId; + + @SerializedName("domainId") + @Param(description = "The Domain Id") + private String domainId; + + @SerializedName("userName") + @Param(description = "The User Name") + private String userName; + + @SerializedName("accountName") + @Param(description = "The Account Name") + private String accountName; + + @SerializedName("domainName") + @Param(description = "The Domain Name") + private String domainName; + + @SerializedName("idpId") + @Param(description = "The IDP ID") + private String idpId; + + public SamlUserAccountResponse() { + super(); + setObjectName("samluseraccount"); + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getDomainId() { + return domainId; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public String getDomainName() { + return domainName; + } + + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + public String getIdpId() { + return idpId; + } + + public void setIdpId(String idpId) { + this.idpId = idpId; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/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 7232ac9..46b1c40 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 @@ -26,6 +26,7 @@ import com.cloud.utils.component.AdapterBase; import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator; import org.apache.cloudstack.api.command.AuthorizeSAMLSSOCmd; import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd; +import org.apache.cloudstack.api.command.ListAndSwitchSAMLAccountCmd; import org.apache.cloudstack.api.command.ListIdpsCmd; import org.apache.cloudstack.api.command.ListSamlAuthorizationCmd; import org.apache.cloudstack.api.command.SAML2LoginAPIAuthenticatorCmd; @@ -491,6 +492,7 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class); cmdList.add(GetServiceProviderMetaDataCmd.class); cmdList.add(ListIdpsCmd.class); + cmdList.add(ListAndSwitchSAMLAccountCmd.class); return cmdList; } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/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 5c8a390..65a7959 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 @@ -23,18 +23,9 @@ import com.cloud.user.dao.UserDao; import com.cloud.utils.Pair; import org.apache.cxf.common.util.StringUtils; import org.apache.log4j.Logger; -import org.opensaml.DefaultBootstrap; -import org.opensaml.saml2.core.Response; -import org.opensaml.saml2.core.StatusCode; -import org.opensaml.xml.ConfigurationException; -import org.opensaml.xml.io.UnmarshallingException; -import org.xml.sax.SAXException; import javax.ejb.Local; import javax.inject.Inject; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.FactoryConfigurationError; -import java.io.IOException; import java.util.Map; @Local(value = {UserAuthenticator.class}) @@ -63,18 +54,7 @@ public class SAML2UserAuthenticator extends DefaultUserAuthenticator { return new Pair(false, null); } else { User user = _userDao.getUser(userAccount.getId()); - 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(); - responseObject = SAMLUtils.decodeSAMLResponse(samlResponse); - } catch (ConfigurationException | FactoryConfigurationError | ParserConfigurationException | SAXException | IOException | UnmarshallingException e) { - return new Pair(false, null); - } - if (!responseObject.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI)) { - return new Pair(false, null); - } + if (user != null && user.getSource() == User.Source.SAML2 && user.getExternalEntity() != null) { return new Pair(true, null); } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/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 0216ad7..fb2d960 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 @@ -20,6 +20,8 @@ package org.apache.cloudstack.saml; import com.cloud.utils.HttpUtils; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.response.LoginCmdResponse; import org.apache.log4j.Logger; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.x509.X509V1CertificateGenerator; @@ -62,6 +64,8 @@ import org.w3c.dom.Element; import org.xml.sax.SAXException; import javax.security.auth.x500.X500Principal; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -351,4 +355,18 @@ public class SAMLUtils { return certGen.generate(keyPair.getPrivate(), "BC"); } + public static void setupSamlUserCookies(final LoginCmdResponse loginResponse, final HttpServletResponse resp) throws IOException { + resp.addCookie(new Cookie("userid", URLEncoder.encode(loginResponse.getUserId(), HttpUtils.UTF_8))); + resp.addCookie(new Cookie("domainid", URLEncoder.encode(loginResponse.getDomainId(), HttpUtils.UTF_8))); + resp.addCookie(new Cookie("role", URLEncoder.encode(loginResponse.getType(), HttpUtils.UTF_8))); + resp.addCookie(new Cookie("username", URLEncoder.encode(loginResponse.getUsername(), HttpUtils.UTF_8))); + resp.addCookie(new Cookie("account", URLEncoder.encode(loginResponse.getAccount(), HttpUtils.UTF_8))); + String timezone = loginResponse.getTimeZone(); + if (timezone != null) { + resp.addCookie(new Cookie("timezone", URLEncoder.encode(timezone, HttpUtils.UTF_8))); + } + resp.addCookie(new Cookie("userfullname", URLEncoder.encode(loginResponse.getFirstName() + " " + loginResponse.getLastName(), HttpUtils.UTF_8).replace("+", "%20"))); + resp.addHeader("SET-COOKIE", String.format("%s=%s;HttpOnly", ApiConstants.SESSIONKEY, loginResponse.getSessionKey())); + } + } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/ui/index.jsp ---------------------------------------------------------------------- diff --git a/ui/index.jsp b/ui/index.jsp index c3bae19..3b7d310 100644 --- a/ui/index.jsp +++ b/ui/index.jsp @@ -75,13 +75,6 @@ -
-
- - -
-
-
" /> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b6782a05/ui/scripts/ui-custom/login.js ---------------------------------------------------------------------- diff --git a/ui/scripts/ui-custom/login.js b/ui/scripts/ui-custom/login.js index e112958..5825529 100644 --- a/ui/scripts/ui-custom/login.js +++ b/ui/scripts/ui-custom/login.js @@ -122,8 +122,7 @@ } else if (selectedLogin === 'saml') { // SAML args.samlLoginAction({ - data: {'idpid': $login.find('#login-options').find(':selected').val(), - 'domain': $login.find('#saml-domain').val()} + data: {'idpid': $login.find('#login-options').find(':selected').val()} }); } return false; @@ -133,16 +132,13 @@ var toggleLoginView = function (selectedOption) { $login.find('#login-submit').show(); if (selectedOption === '') { - $login.find('#saml-login').hide(); $login.find('#cloudstack-login').hide(); $login.find('#login-submit').hide(); selectedLogin = 'none'; } else if (selectedOption === 'cloudstack-login') { - $login.find('#saml-login').hide(); $login.find('#cloudstack-login').show(); selectedLogin = 'cloudstack'; } else { - $login.find('#saml-login').show(); $login.find('#cloudstack-login').hide(); selectedLogin = 'saml'; } @@ -160,14 +156,12 @@ $login.find('#login-dropdown').hide(); $login.find('#login-submit').show(); $login.find('#cloudstack-login').show(); - $login.find('#saml-login').hide(); // If any IdP servers were set, SAML is enabled if (g_idpList && g_idpList.length > 0) { $login.find('#login-dropdown').show(); $login.find('#login-submit').hide(); $login.find('#cloudstack-login').hide(); - $login.find('#saml-login').hide(); $login.find('#login-options') .append($('