Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/src/org/apache/cloudstack/api/ApiServerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public interface ApiServerService {
public ResponseObject loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, InetAddress loginIpAddress,
Map<String, Object[]> 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);
Expand Down
21 changes: 21 additions & 0 deletions api/src/org/apache/cloudstack/api/response/LoginCmdResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<? extends ControlledEntity> arg0, Long arg1,
Expand Down
89 changes: 89 additions & 0 deletions server/src/com/cloud/api/ApiServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't actually enough to make the UI update all the various user fields. So when the the user goes to the UI they still have the name of the admin that was doing the impersonation.

Copy link
Author

@bharkarampudi bharkarampudi Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree. In order to reflect this change in the complete web UI page then there are some UI files need to be updated with the session cookie values. But I was asked to do only API session related changes. If UI changes are required I can do that. what is your recommendation on that?

session.getId(), account, domain_UUID, type, sessionkey, timezone, timezoneoffset, firstname + " " + lastname, user_UUID, username));
}
response.setResponseName("loginresponse");
return response;
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 14 additions & 1 deletion server/src/com/cloud/api/ApiServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public List<Class<?>> getCommands() {
List<Class<?>> cmdList = new ArrayList<Class<?>>();
cmdList.add(DefaultLoginAPIAuthenticatorCmd.class);
cmdList.add(DefaultLogoutAPIAuthenticatorCmd.class);
cmdList.add(ImpersonateUserCmd.class);
for (PluggableAPIAuthenticator apiAuthenticator: _apiAuthenticators) {
List<Class<?>> commands = apiAuthenticator.getAuthCommands();
if (commands != null) {
Expand Down
Loading