cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bhais...@apache.org
Subject [2/8] git commit: updated refs/heads/master to 869a83f
Date Fri, 14 Aug 2015 06:54:43 GMT
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 <rohit.yadav@shapeblue.com>

Conflicts:
	plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
	ui/index.jsp


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/1ec4d015
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/1ec4d015
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/1ec4d015

Branch: refs/heads/master
Commit: 1ec4d0155a56cb5d1e2d62a1925b46e4b7050d66
Parents: 89f47ec
Author: Rohit Yadav <rohit.yadav@shapeblue.com>
Authored: Mon Aug 3 12:36:14 2015 +0530
Committer: Rohit Yadav <rohit.yadav@shapeblue.com>
Committed: Fri Aug 14 11:58:44 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  |  35 +---
 .../api/response/SamlUserAccountResponse.java   |  99 ++++++++++
 .../cloudstack/saml/SAML2AuthManagerImpl.java   |   2 +
 .../cloudstack/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(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/client/tomcatconf/commands.properties.in
----------------------------------------------------------------------
diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in
index c32ecc4..aec7516 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/1ec4d015/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 75353f2..a6cefb1 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");
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/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<String, Object[]> 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<UserAccountVO> switchableAccounts = _userAccountDao.getAllUsersByNameAndEntity(currentUserAccount.getUsername(),
currentUserAccount.getExternalEntity());
+            if (switchableAccounts != null && switchableAccounts.size() > 0 &&
currentUserId != User.UID_SYSTEM) {
+                List<SamlUserAccountResponse> accountResponses = new ArrayList<SamlUserAccountResponse>();
+                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<SamlUserAccountResponse> response = new ListResponse<SamlUserAccountResponse>();
+                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<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
listAndSwitchSamlAccount Cmd");
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/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 ad38704..026a0ca 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/1ec4d015/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 8e67408..f2fcf59 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<UserAccountVO> 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) {
                     }
                 }
             }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/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/1ec4d015/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 b1449c1..ff644f3 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;
     }
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/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<Boolean, ActionOnFailedAuthentication>(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<Boolean, ActionOnFailedAuthentication>(false, null);
-                }
-                if (!responseObject.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI))
{
-                    return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
-                }
+            if (user != null && user.getSource() == User.Source.SAML2 &&
user.getExternalEntity() != null) {
                 return new Pair<Boolean, ActionOnFailedAuthentication>(true, null);
             }
         }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/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 77714a1..ec6b2c1 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()));
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/ui/css/cloudstack3.css
----------------------------------------------------------------------
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 2bcd5e5..60ac9ca 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;

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/ui/index.jsp
----------------------------------------------------------------------
diff --git a/ui/index.jsp b/ui/index.jsp
index e062799..0100744 100644
--- a/ui/index.jsp
+++ b/ui/index.jsp
@@ -75,13 +75,6 @@
                             </div>
                         </div>
 
-                        <div id="saml-login">
-                            <div class="field domain">
-                                <label for="saml-domain"><fmt:message key="label.domain"/></label>
-                                <input id="saml-domain" type="text" name="saml-domain"
/>
-                            </div>
-                        </div>
-
                         <div id="login-submit">
                             <!-- Submit (login) -->
                             <input id="login-submit" type="submit" value="<fmt:message
key="label.login"/>" />
@@ -1841,6 +1834,7 @@
         <script type="text/javascript" src="scripts/docs.js"></script>
         <script type="text/javascript" src="scripts/vm_snapshots.js"></script>
         <script type="text/javascript" src="scripts/ui-custom/projectSelect.js"></script>
+        <script type="text/javascript" src="scripts/ui-custom/saml.js"></script>
 
         <!-- Plugin/module API -->
         <script type="text/javascript" src="scripts/ui-custom/pluginListing.js"></script>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/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($('<option>', {

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/1ec4d015/ui/scripts/ui-custom/saml.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui-custom/saml.js b/ui/scripts/ui-custom/saml.js
new file mode 100644
index 0000000..391e7f8
--- /dev/null
+++ b/ui/scripts/ui-custom/saml.js
@@ -0,0 +1,96 @@
+// 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.
+
+(function($, cloudStack) {
+    $(window).bind('cloudStack.ready', function() {
+        var showSamlDomainSwitcher = false;
+        if (g_idpList) {
+            showSamlDomainSwitcher = true;
+        }
+        if (!showSamlDomainSwitcher) {
+            return;
+        }
+
+        var $label = $('<label>').html('Domain:');
+        var $header = $('#header .controls');
+        var $domainSwitcher = $('<div>').addClass('domain-switcher');
+        var $domainSelect = $('<select>');
+        $domainSwitcher.append($label, $domainSelect);
+
+        var switchAccount = function(userId, domainId) {
+            var toReload = true;
+            $.ajax({
+                url: createURL('listAndSwitchSamlAccount'),
+                type: 'POST',
+                async: false,
+                data: {
+                    userid: userId,
+                    domainid: domainId
+                },
+                success: function(data, textStatus) {
+                    document.location.reload(true);
+                },
+                error: function(data) {
+                    cloudStack.dialog.notice({
+                        message: parseXMLHttpResponse(data)
+                    });
+                    if (data.status !== 200) {
+                        toReload = false;
+                    }
+                },
+                complete: function() {
+                    if (toReload) {
+                        document.location.reload(true);
+                    }
+                    toReload = true;
+                }
+            });
+        };
+
+        $domainSelect.change(function() {
+            var selectedOption = $domainSelect.val();
+            var userId = selectedOption.split('/')[0];
+            var domainId = selectedOption.split('/')[1];
+            switchAccount(userId, domainId);
+        });
+
+        $.ajax({
+            url: createURL('listAndSwitchSamlAccount'),
+            success: function(json) {
+                var accounts = json.listandswitchsamlaccountresponse.samluseraccount;
+                if (accounts.length < 2) {
+                    return;
+                };
+                $domainSelect.empty();
+                for (var i = 0; i < accounts.length; i++) {
+                    var option = $('<option>');
+                    option.data("userId", accounts[i].userId);
+                    option.data("domainId", accounts[i].domainId);
+                    option.val(accounts[i].userId + '/' + accounts[i].domainId);
+                    option.html(accounts[i].accountName + "/" + accounts[i].domainName);
+                    option.appendTo($domainSelect);
+                }
+                var currentAccountDomain = g_userid + '/' + g_domainid;
+                $domainSelect.find('option[value="' + currentAccountDomain + '"]').attr("selected",
"selected");
+                $domainSwitcher.insertAfter($header.find('.region-switcher'));
+            },
+            error: function(data) {
+                // if call fails, the logged in user in not a SAML authenticated user
+            }
+        });
+    });
+}(jQuery, cloudStack));


Mime
View raw message