mirror of https://github.com/apache/cloudstack.git
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 1065661cd5)
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
This commit is contained in:
parent
89f47ece3d
commit
1ec4d0155a
|
|
@ -29,6 +29,7 @@ getSPMetadata=15
|
|||
listIdps=15
|
||||
authorizeSamlSso=7
|
||||
listSamlAuthorization=7
|
||||
listAndSwitchSamlAccount=15
|
||||
|
||||
### Account commands
|
||||
createAccount=7
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,4 +111,4 @@ public class ListIdpsCmd extends BaseCmd implements APIAuthenticator {
|
|||
s_logger.error("No suitable Pluggable Authentication Manager found for SAML2 Login Cmd");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>', {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
Loading…
Reference in New Issue