diff --git a/api/src/org/apache/cloudstack/api/ApiServerService.java b/api/src/org/apache/cloudstack/api/ApiServerService.java index 382b48a5e026..bd2b184eb60a 100644 --- a/api/src/org/apache/cloudstack/api/ApiServerService.java +++ b/api/src/org/apache/cloudstack/api/ApiServerService.java @@ -31,6 +31,8 @@ public interface ApiServerService { public ResponseObject loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, InetAddress loginIpAddress, Map requestParameters) throws CloudAuthenticationException; + public ResponseObject impersonateUser(HttpSession session, String username, Long domainId, String domainPath, InetAddress loginIpAddress) throws CloudAuthenticationException; + public void logoutUser(long userId); public boolean verifyUser(Long userId); diff --git a/api/src/org/apache/cloudstack/api/response/LoginCmdResponse.java b/api/src/org/apache/cloudstack/api/response/LoginCmdResponse.java index d2d122efb665..e8d980394893 100644 --- a/api/src/org/apache/cloudstack/api/response/LoginCmdResponse.java +++ b/api/src/org/apache/cloudstack/api/response/LoginCmdResponse.java @@ -69,6 +69,13 @@ public class LoginCmdResponse extends AuthenticationCmdResponse { @SerializedName(value = ApiConstants.SESSIONKEY) @Param(description = "Session key that can be passed in subsequent Query command calls", isSensitive = true) private String sessionKey; + @SerializedName("cookie") + @Param(description = "Cookie value") + private String cookie; + + @SerializedName("description") + @Param(description = "Description for the response") + private String description; public String getUsername() { return username; @@ -163,4 +170,18 @@ public String getSessionKey() { public void setSessionKey(String sessionKey) { this.sessionKey = sessionKey; } + + public String getCookie(){ return cookie; } + + public void setCookie(String cookie) { + this.cookie = cookie; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } } diff --git a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java index 37ca2bccff48..a3a2964e5d2b 100644 --- a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java +++ b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java @@ -261,6 +261,11 @@ public UserAccount authenticateUser(String arg0, String arg1, Long arg2, InetAdd return null; } + @Override + public UserAccount impersonateUser(String username, Long domainId, InetAddress loginIpAddress) { + return null; + } + @Override public void buildACLSearchBuilder( SearchBuilder arg0, Long arg1, diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java index a97984a2be21..3f34e804eb5c 100644 --- a/server/src/com/cloud/api/ApiServer.java +++ b/server/src/com/cloud/api/ApiServer.java @@ -994,6 +994,15 @@ private ResponseObject createLoginResponse(HttpSession session) { session.removeAttribute("user_UUID"); session.removeAttribute("domain_UUID"); } + String username = null; + String account = null; + String firstname = null; + String lastname = null; + String type = null; + String timezone = null; + String timezoneoffset = null; + String registered = null; + String sessionkey = null; final Enumeration attrNames = session.getAttributeNames(); if (attrNames != null) { @@ -1002,32 +1011,43 @@ private ResponseObject createLoginResponse(HttpSession session) { final Object attrObj = session.getAttribute(attrName); if (ApiConstants.USERNAME.equalsIgnoreCase(attrName)) { response.setUsername(attrObj.toString()); + username = attrObj.toString(); } if (ApiConstants.ACCOUNT.equalsIgnoreCase(attrName)) { response.setAccount(attrObj.toString()); + account = attrObj.toString(); } if (ApiConstants.FIRSTNAME.equalsIgnoreCase(attrName)) { response.setFirstName(attrObj.toString()); + firstname = attrObj.toString(); } if (ApiConstants.LASTNAME.equalsIgnoreCase(attrName)) { response.setLastName(attrObj.toString()); + lastname = attrObj.toString(); } if (ApiConstants.TYPE.equalsIgnoreCase(attrName)) { response.setType((attrObj.toString())); + type = attrObj.toString(); } if (ApiConstants.TIMEZONE.equalsIgnoreCase(attrName)) { response.setTimeZone(attrObj.toString()); + timezone = attrObj.toString(); } if (ApiConstants.TIMEZONEOFFSET.equalsIgnoreCase(attrName)) { response.setTimeZoneOffset(attrObj.toString()); + timezoneoffset = attrObj.toString(); } if (ApiConstants.REGISTERED.equalsIgnoreCase(attrName)) { response.setRegistered(attrObj.toString()); + registered = attrObj.toString(); } if (ApiConstants.SESSIONKEY.equalsIgnoreCase(attrName)) { response.setSessionKey(attrObj.toString()); + sessionkey = attrObj.toString(); } } + response.setCookie(String.format("JSESSIONID=%s;account=%s;domainid=%s;role=%s;sessionkey=%s;timezone=%s;timezoneoffset=%s;userfullname=%s;userid=%s;username=%s", + session.getId(), account, domain_UUID, type, sessionkey, timezone, timezoneoffset, firstname + " " + lastname, user_UUID, username)); } response.setResponseName("loginresponse"); return response; @@ -1103,6 +1123,75 @@ public ResponseObject loginUser(final HttpSession session, final String username throw new CloudAuthenticationException("Failed to authenticate user " + username + " in domain " + domainId + "; please provide valid credentials"); } + @Override + public ResponseObject impersonateUser(final HttpSession session, final String username, Long domainId, final String domainPath, final InetAddress loginIpAddress) throws CloudAuthenticationException { + // We will always use domainId first. If that does not exist, we will use domain name. If THAT doesn't exist + // we will default to ROOT + final Domain userDomain = domainMgr.findDomainByIdOrPath(domainId, domainPath); + if (userDomain == null || userDomain.getId() < 1L) { + throw new CloudAuthenticationException("Unable to find the domain from the path " + domainPath); + } else { + domainId = userDomain.getId(); + } + + final UserAccount userAcct = accountMgr.impersonateUser(username, domainId, loginIpAddress); + if (userAcct != null) { + final String timezone = userAcct.getTimezone(); + float offsetInHrs = 0f; + if (timezone != null) { + final TimeZone t = TimeZone.getTimeZone(timezone); + s_logger.info("Current user logged in under " + timezone + " timezone"); + + final java.util.Date date = new java.util.Date(); + final long longDate = date.getTime(); + final float offsetInMs = (t.getOffset(longDate)); + offsetInHrs = offsetInMs / (1000 * 60 * 60); + s_logger.info("Timezone offset from UTC is: " + offsetInHrs); + } + + final Account account = accountMgr.getAccount(userAcct.getAccountId()); + + // set the userId and account object for everyone + session.setAttribute("userid", userAcct.getId()); + final UserVO user = (UserVO)accountMgr.getActiveUser(userAcct.getId()); + if (user.getUuid() != null) { + session.setAttribute("user_UUID", user.getUuid()); + } + + session.setAttribute("username", userAcct.getUsername()); + session.setAttribute("firstname", userAcct.getFirstname()); + session.setAttribute("lastname", userAcct.getLastname()); + session.setAttribute("accountobj", account); + session.setAttribute("account", account.getAccountName()); + + session.setAttribute("domainid", account.getDomainId()); + final DomainVO domain = (DomainVO)domainMgr.getDomain(account.getDomainId()); + if (domain.getUuid() != null) { + session.setAttribute("domain_UUID", domain.getUuid()); + } + + session.setAttribute("type", Short.valueOf(account.getType()).toString()); + session.setAttribute("registrationtoken", userAcct.getRegistrationToken()); + session.setAttribute("registered", Boolean.toString(userAcct.isRegistered())); + + if (timezone != null) { + session.setAttribute("timezone", timezone); + session.setAttribute("timezoneoffset", Float.valueOf(offsetInHrs).toString()); + } + + // (bug 5483) generate a session key that the user must submit on every request to prevent CSRF, add that + // to the login response so that session-based authenticators know to send the key back + final SecureRandom sesssionKeyRandom = new SecureRandom(); + final byte sessionKeyBytes[] = new byte[20]; + sesssionKeyRandom.nextBytes(sessionKeyBytes); + final String sessionKey = Base64.encodeBase64URLSafeString(sessionKeyBytes); + session.setAttribute(ApiConstants.SESSIONKEY, sessionKey); + + return createLoginResponse(session); + } + throw new CloudAuthenticationException("Failed to authenticate user " + username + " in domain " + domainId + "; please provide valid credentials"); + } + @Override public void logoutUser(final long userId) { accountMgr.logoutUser(userId); diff --git a/server/src/com/cloud/api/ApiServlet.java b/server/src/com/cloud/api/ApiServlet.java index 4002ff8d99b1..c4de33da9046 100644 --- a/server/src/com/cloud/api/ApiServlet.java +++ b/server/src/com/cloud/api/ApiServlet.java @@ -41,6 +41,7 @@ import org.apache.cloudstack.api.auth.APIAuthenticationManager; import org.apache.cloudstack.api.auth.APIAuthenticationType; import org.apache.cloudstack.api.auth.APIAuthenticator; +import com.cloud.api.auth.ImpersonateUserCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.managed.context.ManagedContext; import org.apache.log4j.Logger; @@ -185,6 +186,18 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp final Object[] commandObj = params.get(ApiConstants.COMMAND); if (commandObj != null) { final String command = (String) commandObj[0]; + if (command.equals(ImpersonateUserCmd.APINAME)) { + final Long userId = (Long) session.getAttribute("userid"); + final User user = ApiDBUtils.findUserById(userId); + final Account account = ApiDBUtils.findAccountById(user.getAccountId()); + if (account.getType() != (Account.ACCOUNT_TYPE_ADMIN)) { + s_logger.info(String.format("User does not have permission to access the API")); + auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "User does not have permission to access the API"); + final String serializedResponse = apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "User does not have permission to access the API", params, responseType); + HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType, ApiServer.JSONcontentType.value()); + return; + } + } APIAuthenticator apiAuthenticator = authManager.getAPIAuthenticator(command); if (apiAuthenticator != null) { @@ -194,7 +207,7 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp int httpResponseCode = HttpServletResponse.SC_OK; String responseString = null; - if (apiAuthenticator.getAPIType() == APIAuthenticationType.LOGIN_API) { + if (apiAuthenticator.getAPIType() == APIAuthenticationType.LOGIN_API && !command.equals(ImpersonateUserCmd.APINAME)) { if (session != null) { try { session.invalidate(); diff --git a/server/src/com/cloud/api/auth/APIAuthenticationManagerImpl.java b/server/src/com/cloud/api/auth/APIAuthenticationManagerImpl.java index c304a10d917f..766d184f3167 100644 --- a/server/src/com/cloud/api/auth/APIAuthenticationManagerImpl.java +++ b/server/src/com/cloud/api/auth/APIAuthenticationManagerImpl.java @@ -77,6 +77,7 @@ public List> getCommands() { List> cmdList = new ArrayList>(); cmdList.add(DefaultLoginAPIAuthenticatorCmd.class); cmdList.add(DefaultLogoutAPIAuthenticatorCmd.class); + cmdList.add(ImpersonateUserCmd.class); for (PluggableAPIAuthenticator apiAuthenticator: _apiAuthenticators) { List> commands = apiAuthenticator.getAuthCommands(); if (commands != null) { diff --git a/server/src/com/cloud/api/auth/ImpersonateUserCmd.java b/server/src/com/cloud/api/auth/ImpersonateUserCmd.java new file mode 100644 index 000000000000..4a65f18d2518 --- /dev/null +++ b/server/src/com/cloud/api/auth/ImpersonateUserCmd.java @@ -0,0 +1,194 @@ +// 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 com.cloud.api.auth; + +import com.cloud.domain.Domain; +import com.cloud.user.User; +import com.cloud.user.UserAccount; +import org.apache.cloudstack.api.ApiServerService; +import com.cloud.api.response.ApiResponseSerializer; +import com.cloud.exception.CloudAuthenticationException; +import com.cloud.user.Account; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +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.LoginCmdResponse; +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.List; +import java.util.Map; +import java.net.InetAddress; + +@APICommand(name = "impersonateUser", description = "Logs a user into the CloudStack. A successful login attempt will generate a JSESSIONID cookie value that can be passed in subsequent Query command calls until the \"logout\" command has been issued or the session has expired.", requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, entityType = {}) +public class ImpersonateUserCmd extends BaseCmd implements APIAuthenticator { + + public static final Logger s_logger = Logger.getLogger(ImpersonateUserCmd.class.getName()); + public static final String APINAME = "impersonateUser"; + private static final String s_name = "loginresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, description = "Username", required = true) + private String username; + + @Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING, description = "Path of the domain that the user belongs to. Example: domain=/com/cloud/internal. If no domain is passed in, the ROOT (/) domain is assumed.") + private String domain; + + @Parameter(name = ApiConstants.DOMAIN__ID, type = CommandType.LONG, description = "The id of the domain that the user belongs to. If both domain and domainId are passed in, \"domainId\" parameter takes precendence") + private Long domainId; + + @Inject + ApiServerService _apiServer; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getUsername() { + return username; + } + + public String getDomain() { + return domain; + } + + public Long getDomainId() { + return domainId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_TYPE_NORMAL; + } + + @Override + public void execute() throws ServerApiException { + // We should never reach here + throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly"); + } + + @Override + public String authenticate(String command, Map params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException { + // FIXME: ported from ApiServlet, refactor and cleanup + final String[] username = (String[])params.get(ApiConstants.USERNAME); + String[] domainIdArr = (String[])params.get(ApiConstants.DOMAIN_ID); + + if (domainIdArr == null) { + domainIdArr = (String[])params.get(ApiConstants.DOMAIN__ID); + } + final String[] domainName = (String[])params.get(ApiConstants.DOMAIN); + Long domainId = null; + if ((domainIdArr != null) && (domainIdArr.length > 0)) { + try { + //check if UUID is passed in for domain + domainId = _apiServer.fetchDomainId(domainIdArr[0]); + if (domainId == null) { + domainId = Long.parseLong(domainIdArr[0]); + } + auditTrailSb.append(" domainid=" + domainId);// building the params for POST call + } catch (final NumberFormatException e) { + s_logger.warn("Invalid domain id entered by user"); + auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "Invalid domain id entered, please enter a valid one"); + throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, + _apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid domain id entered, please enter a valid one", params, + responseType)); + } + } + + String domain = null; + if (domainName != null) { + domain = domainName[0]; + auditTrailSb.append(" domain=" + domain); + if (domain != null) { + // ensure domain starts with '/' and ends with '/' + if (!domain.endsWith("/")) { + domain += '/'; + } + if (!domain.startsWith("/")) { + domain = "/" + domain; + } + } + } + + String serializedResponse = null; + if (username != null) { + try { + final Domain userDomain = _domainService.findDomainByIdOrPath(domainId, domain); + if (userDomain != null) { + domainId = userDomain.getId(); + } else { + LoginCmdResponse response = new LoginCmdResponse(); + response.setResponseName("loginresponse"); + response.setDescription("Unable to find the domain from the path " + domain); + return ApiResponseSerializer.toSerializedString(response, responseType); + } + final UserAccount userAccount = _accountService.getActiveUserAccount(username[0], domainId); + if (userAccount != null && User.Source.SAML2 == userAccount.getSource()) { + LoginCmdResponse response = new LoginCmdResponse(); + response.setResponseName("loginresponse"); + response.setDescription("User is not allowed CloudStack login"); + return ApiResponseSerializer.toSerializedString(response, responseType); + } + + return ApiResponseSerializer.toSerializedString(_apiServer.impersonateUser(session, username[0], domainId, domain, remoteAddress), + responseType); + } catch (final CloudAuthenticationException ex) { + // TODO: fall through to API key, or just fail here w/ auth error? (HTTP 401) + try { + session.invalidate(); + } catch (final IllegalStateException ise) { + } + auditTrailSb.append(" " + ApiErrorCode.ACCOUNT_ERROR + " " + ex.getMessage() != null ? ex.getMessage() + : "failed to authenticate user, check if username/password are correct"); + serializedResponse = + _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), ex.getMessage() != null ? ex.getMessage() + : "failed to authenticate user, check if username/password are correct", params, responseType); + } + } + // We should not reach here and if we do we throw an exception + throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, serializedResponse); + } + + @Override + public APIAuthenticationType getAPIType() { + return APIAuthenticationType.LOGIN_API; + } + + @Override + public void setAuthenticators(List authenticators) { + } +} diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index a7dfc205b735..37033d40b0ef 100644 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -37,6 +37,9 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; + +import com.cloud.api.auth.ImpersonateUserCmd; + import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.affinity.AffinityGroupProcessor; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; @@ -3096,6 +3099,7 @@ public List> getCommands() { cmdList.add(IssueOutOfBandManagementPowerActionCmd.class); cmdList.add(ChangeOutOfBandManagementPasswordCmd.class); cmdList.add(GetUserKeysCmd.class); + cmdList.add(ImpersonateUserCmd.class); return cmdList; } diff --git a/server/src/com/cloud/user/AccountManager.java b/server/src/com/cloud/user/AccountManager.java index e708b040ed92..fe10cddb0167 100644 --- a/server/src/com/cloud/user/AccountManager.java +++ b/server/src/com/cloud/user/AccountManager.java @@ -77,6 +77,9 @@ public interface AccountManager extends AccountService, Configurable{ */ UserAccount authenticateUser(String username, String password, Long domainId, InetAddress loginIpAddress, Map requestParameters); + + UserAccount impersonateUser(String username, Long domainId, InetAddress loginIpAddress); + /** * Locate a user by their apiKey * diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index 9129976900d3..7bb8d83165eb 100644 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -2248,6 +2248,39 @@ public UserAccount authenticateUser(final String username, final String password } } + @Override + public UserAccount impersonateUser(final String username, final Long domainId, final InetAddress loginIpAddress) { + UserAccount user = _userAccountDao.getUserAccount(username, domainId); + if (user != null) { + // don't allow to authenticate system user + if (user.getId() == User.UID_SYSTEM) { + s_logger.error("Failed to authenticate user: " + username + " in domain " + domainId); + return null; + } + // don't allow baremetal system user + if (BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME.equals(user.getUsername())) { + s_logger.error("Won't authenticate user: " + username + " in domain " + domainId); + return null; + } + // We authenticated successfully by now, let's check if we are allowed to login from the ip address the reqest comes from + final Account account = _accountMgr.getAccount(user.getAccountId()); + final DomainVO domain = (DomainVO) _domainMgr.getDomain(account.getDomainId()); + + // Here all is fine! + if (s_logger.isDebugEnabled()) { + s_logger.debug("User: " + username + " in domain " + domainId + " has successfully logged in"); + } + ActionEventUtils.onActionEvent(user.getId(), user.getAccountId(), user.getDomainId(), EventTypes.EVENT_USER_LOGIN, + "user has logged in from IP Address " + loginIpAddress); + return user; + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("User: " + username + " in domain " + domainId + " has failed to log in"); + } + return null; + } + } + private UserAccount getUserAccount(String username, String password, Long domainId, Map requestParameters) { if (s_logger.isDebugEnabled()) { s_logger.debug("Attempting to log in user: " + username + " in domain " + domainId); diff --git a/server/src/com/cloud/user/DomainManagerImpl.java b/server/src/com/cloud/user/DomainManagerImpl.java index 60c48fa574e8..583e53e144de 100644 --- a/server/src/com/cloud/user/DomainManagerImpl.java +++ b/server/src/com/cloud/user/DomainManagerImpl.java @@ -236,7 +236,7 @@ public DomainVO findDomainByPath(String domainPath) { public Domain findDomainByIdOrPath(final Long id, final String domainPath) { Long domainId = id; if (domainId == null || domainId < 1L) { - if (Strings.isNullOrEmpty(domainPath) || domainPath.trim().isEmpty()) { + if (Strings.isNullOrEmpty(domainPath) || domainPath.trim().isEmpty() || domainPath.trim().equalsIgnoreCase("/ROOT/")) { domainId = Domain.ROOT_DOMAIN; } else { final Domain domainVO = findDomainByPath(domainPath.trim()); diff --git a/server/test/com/cloud/user/MockAccountManagerImpl.java b/server/test/com/cloud/user/MockAccountManagerImpl.java index 0a92c1446dbc..2be330176e95 100644 --- a/server/test/com/cloud/user/MockAccountManagerImpl.java +++ b/server/test/com/cloud/user/MockAccountManagerImpl.java @@ -278,6 +278,11 @@ public UserAccount authenticateUser(String username, String password, Long domai return null; } + @Override + public UserAccount impersonateUser(String username, Long domainId, InetAddress loginIpAddress) { + return null; + } + @Override public Pair findUserByApiKey(String apiKey) { return null;