From 1ec4d0155a56cb5d1e2d62a1925b46e4b7050d66 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 3 Aug 2015 12:36:14 +0530 Subject: [PATCH] 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. Moves the previous switch account logic to its own ui-custom module (cherry picked from commit 1065661cd50c8d43bf65644a13d164b96732b011) Signed-off-by: Rohit Yadav Conflicts: plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java ui/index.jsp --- client/tomcatconf/commands.properties.in | 1 + .../GetServiceProviderMetaDataCmd.java | 2 +- .../command/ListAndSwitchSAMLAccountCmd.java | 195 ++++++++++++++++++ .../cloudstack/api/command/ListIdpsCmd.java | 2 +- .../SAML2LoginAPIAuthenticatorCmd.java | 35 +--- .../api/response/SamlUserAccountResponse.java | 99 +++++++++ .../cloudstack/saml/SAML2AuthManagerImpl.java | 2 + .../saml/SAML2UserAuthenticator.java | 22 +- .../org/apache/cloudstack/saml/SAMLUtils.java | 18 ++ ui/css/cloudstack3.css | 6 +- ui/index.jsp | 8 +- ui/scripts/ui-custom/login.js | 8 +- ui/scripts/ui-custom/saml.js | 96 +++++++++ 13 files changed, 424 insertions(+), 70 deletions(-) create mode 100644 plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/ListAndSwitchSAMLAccountCmd.java create mode 100644 plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/response/SamlUserAccountResponse.java create mode 100644 ui/scripts/ui-custom/saml.js diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index c32ecc4bdfb..aec751658b3 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 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 75353f2c3c4..a6cefb1baf9 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 @@ -267,7 +267,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"); } } } 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 00000000000..db0d6dc7373 --- /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 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 ad387044b5f..026a0ca93c7 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 +} 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 8e67408b5f0..f2fcf59c856 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,7 +62,6 @@ 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; @@ -197,7 +194,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()); @@ -206,9 +202,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", @@ -300,17 +293,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())) { @@ -324,21 +309,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) { } } } 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 00000000000..f0927e3f6d3 --- /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; + } +} 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 b1449c1a644..ff644f34948 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; @@ -506,6 +507,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; } 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 5c8a39088ae..65a7959d997 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); } } 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 77714a1f6a4..ec6b2c11e5e 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; @@ -352,4 +356,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())); + } + } diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 2bcd5e5a158..60ac9ca4d64 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -9657,7 +9657,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal } /*** View switcher (drop-down)*/ -.project-switcher { +.project-switcher, .domain-switcher { float: left; width: 223px; padding: 9px 17px 0 19px; @@ -9668,7 +9668,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal border-radius: 4px; } -.project-switcher label { +.project-switcher label, .domain-switcher label { top: 29px; color: #FFFFFF; font-size: 13px; @@ -9677,7 +9677,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal margin-top: 5px; } -.project-switcher select { +.project-switcher select, .domain-switcher select { width: 70%; float: left; margin-top: 0px; diff --git a/ui/index.jsp b/ui/index.jsp index e062799618b..01007441cf3 100644 --- a/ui/index.jsp +++ b/ui/index.jsp @@ -75,13 +75,6 @@ -
-
- - -
-
-
" /> @@ -1841,6 +1834,7 @@ + diff --git a/ui/scripts/ui-custom/login.js b/ui/scripts/ui-custom/login.js index e1129583454..58255297a48 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($('