API to list console sessions (#11016)

* create API to list console sessions
This commit is contained in:
Bernardo De Marco Gonçalves 2025-08-01 11:58:33 -03:00 committed by GitHub
parent 5c1bf4a4ce
commit 9de77e1cc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1218 additions and 9 deletions

View File

@ -23,6 +23,7 @@ public class ApiConstants {
public static final String ACCOUNT_ID = "accountid";
public static final String ACCOUNT_IDS = "accountids";
public static final String ACCUMULATE = "accumulate";
public static final String ACQUIRED = "acquired";
public static final String ACTIVATION_RULE = "activationrule";
public static final String ACTIVITY = "activity";
public static final String ADAPTER_TYPE = "adaptertype";
@ -94,9 +95,11 @@ public class ApiConstants {
public static final String CONVERT_INSTANCE_HOST_ID = "convertinstancehostid";
public static final String CONVERT_INSTANCE_STORAGE_POOL_ID = "convertinstancepoolid";
public static final String ENABLED_REVOCATION_CHECK = "enabledrevocationcheck";
public static final String CLIENT_ADDRESS = "clientaddress";
public static final String COMBINED_CAPACITY_ORDERING = "COMBINED";
public static final String CONTROLLER = "controller";
public static final String CONTROLLER_UNIT = "controllerunit";
public static final String CONSOLE_ENDPOINT_CREATOR_ADDRESS = "consoleendpointcreatoraddress";
public static final String COPY_IMAGE_TAGS = "copyimagetags";
public static final String CPU_OVERCOMMIT_RATIO = "cpuOvercommitRatio";
public static final String CSR = "csr";

View File

@ -22,6 +22,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
import org.apache.cloudstack.consoleproxy.ConsoleSession;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.api.ApiConstants.HostDetails;
@ -579,4 +581,6 @@ public interface ResponseGenerator {
void updateTemplateIsoResponsesForIcons(List<TemplateResponse> responses, ResourceTag.ResourceObjectType type);
GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin);
ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView);
}

View File

@ -0,0 +1,182 @@
// 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.import org.apache.cloudstack.context.CallContext;
package org.apache.cloudstack.api.command.user.consoleproxy;
import org.apache.cloudstack.consoleproxy.ConsoleSession;
import com.cloud.user.Account;
import com.cloud.user.AccountService;
import com.cloud.user.UserAccount;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.UserResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.consoleproxy.ConsoleAccessManager;
import javax.inject.Inject;
import java.util.Date;
@APICommand(name = "listConsoleSessions", description = "Lists console sessions.", responseObject = ConsoleSessionResponse.class,
entityType = {ConsoleSession.class}, since = "4.21.0",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User})
public class ListConsoleSessionsCmd extends BaseListCmd {
@Inject
private AccountService accountService;
@Inject
private ConsoleAccessManager consoleAccessManager;
@ACL
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ConsoleSessionResponse.class, description = "The ID of the console session.")
private Long id;
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "The domain ID of the account that created the console endpoint.")
private Long domainId;
@ACL
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "The ID of the account that created the console endpoint.")
private Long accountId;
@ACL
@Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "The ID of the user that created the console endpoint.")
private Long userId;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, authorized = {RoleType.Admin}, description = "Lists console sessions from the specified host.")
private Long hostId;
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Lists console sessions generated from this date onwards. " +
ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
private Date startDate;
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Lists console sessions generated up until this date. " +
ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
private Date endDate;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, description = "The ID of the virtual machine.")
private Long vmId;
@Parameter(name = ApiConstants.CONSOLE_ENDPOINT_CREATOR_ADDRESS, type = CommandType.STRING, description = "IP address of the creator of the console endpoint.")
private String consoleEndpointCreatorAddress;
@Parameter(name = ApiConstants.CLIENT_ADDRESS, type = CommandType.STRING, description = "IP address of the client that accessed the console session.")
private String clientAddress;
@Parameter(name = ApiConstants.ACTIVE_ONLY, type = CommandType.BOOLEAN,
description = "Lists only active console sessions, defaults to true. Active sessions are the ones that have been acquired and have not been removed.")
private boolean activeOnly = true;
@Parameter(name = ApiConstants.ACQUIRED, type = CommandType.BOOLEAN,
description = "Lists acquired console sessions, defaults to false. Acquired console sessions are the ones that have been accessed. " +
"The 'activeonly' parameter has precedence over the 'acquired' parameter, i.e., when the 'activeonly' parameter is 'true', the 'acquired' parameter value will be ignored.")
private boolean acquired = false;
@Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN,
description = "Lists console sessions recursively per domain. If an account ID is informed, only the account's console sessions will be listed. Defaults to false.")
private boolean recursive = false;
public Long getId() {
return id;
}
public Long getDomainId() {
return domainId;
}
public Long getAccountId() {
return accountId;
}
public Long getUserId() {
return userId;
}
public Long getHostId() {
return hostId;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public Long getVmId() {
return vmId;
}
public String getConsoleEndpointCreatorAddress() {
return consoleEndpointCreatorAddress;
}
public String getClientAddress() {
return clientAddress;
}
public boolean isActiveOnly() {
return activeOnly;
}
public boolean getAcquired() {
return acquired;
}
public boolean isRecursive() {
return recursive;
}
@Override
public void execute() {
ListResponse<ConsoleSessionResponse> response = consoleAccessManager.listConsoleSessions(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
if (getId() != null) {
ConsoleSession consoleSession = consoleAccessManager.listConsoleSessionById(getId());
if (consoleSession != null) {
return consoleSession.getAccountId();
}
}
if (getAccountId() != null) {
return getAccountId();
}
if (getUserId() != null) {
UserAccount userAccount = accountService.getUserAccountById(getUserId());
if (userAccount != null) {
return userAccount.getAccountId();
}
}
return Account.ACCOUNT_ID_SYSTEM;
}
}

View File

@ -0,0 +1,236 @@
// 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.import org.apache.cloudstack.context.CallContext;
package org.apache.cloudstack.api.response;
import com.google.gson.annotations.SerializedName;
import com.cloud.serializer.Param;
import org.apache.cloudstack.consoleproxy.ConsoleSession;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import java.util.Date;
@EntityReference(value = ConsoleSession.class)
public class ConsoleSessionResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "ID of the console session.")
private String id;
@SerializedName(ApiConstants.CREATED)
@Param(description = "Date when the console session's endpoint was created.")
private Date created;
@SerializedName(ApiConstants.DOMAIN)
@Param(description = "Domain of the account that created the console endpoint.")
private String domain;
@SerializedName(ApiConstants.DOMAIN_PATH)
@Param(description = "Domain path of the account that created the console endpoint.")
private String domainPath;
@SerializedName(ApiConstants.DOMAIN_ID)
@Param(description = "Domain ID of the account that created the console endpoint.")
private String domainId;
@SerializedName(ApiConstants.ACCOUNT)
@Param(description = "Account that created the console endpoint.")
private String account;
@SerializedName(ApiConstants.ACCOUNT_ID)
@Param(description = "ID of the account that created the console endpoint.")
private String accountId;
@SerializedName(ApiConstants.USER)
@Param(description = "User that created the console endpoint.")
private String user;
@SerializedName(ApiConstants.USER_ID)
@Param(description = "ID of the user that created the console endpoint.")
private String userId;
@SerializedName(ApiConstants.VIRTUAL_MACHINE_ID)
@Param(description = "ID of the virtual machine.")
private String vmId;
@SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME)
@Param(description = "Name of the virtual machine.")
private String vmName;
@SerializedName(ApiConstants.HOST_ID)
@Param(description = "ID of the host.")
private String hostId;
@SerializedName(ApiConstants.HOST_NAME)
@Param(description = "Name of the host.")
private String hostName;
@SerializedName(ApiConstants.ACQUIRED)
@Param(description = "Date when the console session was acquired.")
private Date acquired;
@SerializedName(ApiConstants.REMOVED)
@Param(description = "Date when the console session was removed.")
private Date removed;
@SerializedName(ApiConstants.CONSOLE_ENDPOINT_CREATOR_ADDRESS)
@Param(description = "IP address of the creator of the console endpoint.")
private String consoleEndpointCreatorAddress;
@SerializedName(ApiConstants.CLIENT_ADDRESS)
@Param(description = "IP address of the client that created the console session.")
private String clientAddress;
public void setId(String id) {
this.id = id;
}
public void setCreated(Date created) {
this.created = created;
}
public void setDomain(String domain) {
this.domain = domain;
}
public void setDomainPath(String domainPath) {
this.domainPath = domainPath;
}
public void setDomainId(String domainId) {
this.domainId = domainId;
}
public void setAccount(String account) {
this.account = account;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public void setUser(String user) {
this.user = user;
}
public void setUserId(String userId) {
this.userId = userId;
}
public void setVmId(String vmId) {
this.vmId = vmId;
}
public void setVmName(String vmName) {
this.vmName = vmName;
}
public void setHostId(String hostId) {
this.hostId = hostId;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public void setAcquired(Date acquired) {
this.acquired = acquired;
}
public void setRemoved(Date removed) {
this.removed = removed;
}
public void setConsoleEndpointCreatorAddress(String consoleEndpointCreatorAddress) {
this.consoleEndpointCreatorAddress = consoleEndpointCreatorAddress;
}
public void setClientAddress(String clientAddress) {
this.clientAddress = clientAddress;
}
public String getId() {
return id;
}
public Date getCreated() {
return created;
}
public String getDomain() {
return domain;
}
public String getDomainPath() {
return domainPath;
}
public String getDomainId() {
return domainId;
}
public String getAccount() {
return account;
}
public String getAccountId() {
return accountId;
}
public String getUser() {
return user;
}
public String getUserId() {
return userId;
}
public String getVmId() {
return vmId;
}
public String getVmName() {
return vmName;
}
public String getHostId() {
return hostId;
}
public String getHostName() {
return hostName;
}
public Date getAcquired() {
return acquired;
}
public Date getRemoved() {
return removed;
}
public String getConsoleEndpointCreatorAddress() {
return consoleEndpointCreatorAddress;
}
public String getClientAddress() {
return clientAddress;
}
}

View File

@ -18,6 +18,9 @@ package org.apache.cloudstack.consoleproxy;
import com.cloud.utils.component.Manager;
import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint;
import org.apache.cloudstack.api.command.user.consoleproxy.ListConsoleSessionsCmd;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import java.util.Date;
@ -48,4 +51,8 @@ public interface ConsoleAccessManager extends Manager, Configurable {
String genAccessTicket(String host, String port, String sid, String tag, String sessionUuid);
String genAccessTicket(String host, String port, String sid, String tag, Date normalizedHashTime, String sessionUuid);
ListResponse<ConsoleSessionResponse> listConsoleSessions(ListConsoleSessionsCmd cmd);
ConsoleSession listConsoleSessionById(long id);
}

View File

@ -0,0 +1,45 @@
// 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.consoleproxy;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.util.Date;
public interface ConsoleSession extends InternalIdentity, Identity {
Date getCreated();
long getDomainId();
long getAccountId();
long getUserId();
long getInstanceId();
long getHostId();
Date getRemoved();
Date getAcquired();
String getConsoleEndpointCreatorAddress();
String getClientAddress();
}

View File

@ -0,0 +1,124 @@
// 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.import org.apache.cloudstack.context.CallContext;
package org.apache.cloudstack.api.command.user.consoleproxy;
import org.apache.cloudstack.consoleproxy.ConsoleSession;
import com.cloud.user.AccountService;
import com.cloud.user.UserAccount;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.consoleproxy.ConsoleAccessManager;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ListConsoleSessionsCmdTest {
@Mock
private AccountService accountServiceMock;
@Mock
private ConsoleAccessManager consoleAccessManagerMock;
@Spy
@InjectMocks
private ListConsoleSessionsCmd listConsoleSessionsCmdSpy;
@Test
public void executeTestApiExecutionShouldCallServiceLayer() {
Mockito.when(consoleAccessManagerMock.listConsoleSessions(listConsoleSessionsCmdSpy)).thenReturn(new ListResponse<>());
listConsoleSessionsCmdSpy.execute();
Mockito.verify(consoleAccessManagerMock).listConsoleSessions(listConsoleSessionsCmdSpy);
}
@Test
public void getEntityOwnerIdTestReturnConsoleSessionIdIfProvided() {
ConsoleSession consoleSessionMock = Mockito.mock(ConsoleSession.class);
long consoleSessionId = 2L;
long accountId = 2L;
Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(consoleSessionId);
Mockito.when(consoleAccessManagerMock.listConsoleSessionById(consoleSessionId)).thenReturn(consoleSessionMock);
Mockito.when(consoleSessionMock.getAccountId()).thenReturn(accountId);
Assert.assertEquals(accountId, listConsoleSessionsCmdSpy.getEntityOwnerId());
}
@Test
public void getEntityOwnerIdTestReturnAccountIdWhenNoConsoleSessionIdIsProvided() {
long accountId = 2L;
Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(null);
Mockito.when(listConsoleSessionsCmdSpy.getAccountId()).thenReturn(accountId);
Assert.assertEquals(accountId, listConsoleSessionsCmdSpy.getEntityOwnerId());
}
@Test
public void getEntityOwnerIdTestReturnUserIdWhenNoConsoleSessionIdAndAccountIdAreProvided() {
UserAccount userAccountMock = Mockito.mock(UserAccount.class);
long userId = 2L;
Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(null);
Mockito.when(listConsoleSessionsCmdSpy.getAccountId()).thenReturn(null);
Mockito.when(listConsoleSessionsCmdSpy.getUserId()).thenReturn(userId);
Mockito.when(accountServiceMock.getUserAccountById(userId)).thenReturn(userAccountMock);
Mockito.when(userAccountMock.getAccountId()).thenReturn(userId);
Assert.assertEquals(userId, listConsoleSessionsCmdSpy.getEntityOwnerId());
}
@Test
public void getEntityOwnerIdTestReturnSystemAccountIdWhenNoConsoleSessionIdAndAccountIdAndUserIdAreProvided() {
long systemAccountId = 1L;
Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(null);
Mockito.when(listConsoleSessionsCmdSpy.getAccountId()).thenReturn(null);
Mockito.when(listConsoleSessionsCmdSpy.getUserId()).thenReturn(null);
Assert.assertEquals(systemAccountId, listConsoleSessionsCmdSpy.getEntityOwnerId());
}
@Test
public void getEntityOwnerIdTestReturnSystemAccountIdWhenConsoleSessionDoesNotExist() {
long consoleSessionId = 2L;
long systemAccountId = 1L;
Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(consoleSessionId);
Mockito.when(consoleAccessManagerMock.listConsoleSessionById(consoleSessionId)).thenReturn(null);
Assert.assertEquals(systemAccountId, listConsoleSessionsCmdSpy.getEntityOwnerId());
}
@Test
public void getEntityOwnerIdTestReturnSystemAccountIdWhenUserAccountDoesNotExist() {
long userId = 2L;
long systemAccountId = 1L;
Mockito.when(listConsoleSessionsCmdSpy.getUserId()).thenReturn(userId);
Mockito.when(accountServiceMock.getUserAccountById(userId)).thenReturn(null);
Assert.assertEquals(systemAccountId, listConsoleSessionsCmdSpy.getEntityOwnerId());
}
}

View File

@ -19,6 +19,8 @@
package com.cloud.vm;
import org.apache.cloudstack.consoleproxy.ConsoleSession;
import java.util.Date;
import javax.persistence.Column;
@ -32,7 +34,7 @@ import javax.persistence.TemporalType;
@Entity
@Table(name = "console_session")
public class ConsoleSessionVO {
public class ConsoleSessionVO implements ConsoleSession {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ -45,6 +47,9 @@ public class ConsoleSessionVO {
@Column(name = "created")
private Date created;
@Column(name = "domain_id")
private long domainId;
@Column(name = "account_id")
private long accountId;
@ -86,6 +91,7 @@ public class ConsoleSessionVO {
this.uuid = uuid;
}
@Override
public Date getCreated() {
return created;
}
@ -94,6 +100,16 @@ public class ConsoleSessionVO {
this.created = created;
}
@Override
public long getDomainId() {
return domainId;
}
public void setDomainId(long domainId) {
this.domainId = domainId;
}
@Override
public long getAccountId() {
return accountId;
}
@ -102,6 +118,7 @@ public class ConsoleSessionVO {
this.accountId = accountId;
}
@Override
public long getUserId() {
return userId;
}
@ -110,6 +127,7 @@ public class ConsoleSessionVO {
this.userId = userId;
}
@Override
public long getInstanceId() {
return instanceId;
}
@ -118,6 +136,7 @@ public class ConsoleSessionVO {
this.instanceId = instanceId;
}
@Override
public long getHostId() {
return hostId;
}
@ -126,6 +145,7 @@ public class ConsoleSessionVO {
this.hostId = hostId;
}
@Override
public Date getRemoved() {
return removed;
}
@ -134,6 +154,7 @@ public class ConsoleSessionVO {
this.removed = removed;
}
@Override
public Date getAcquired() {
return acquired;
}
@ -142,6 +163,7 @@ public class ConsoleSessionVO {
this.acquired = acquired;
}
@Override
public String getConsoleEndpointCreatorAddress() {
return consoleEndpointCreatorAddress;
}
@ -150,6 +172,7 @@ public class ConsoleSessionVO {
this.consoleEndpointCreatorAddress = consoleEndpointCreatorAddress;
}
@Override
public String getClientAddress() {
return clientAddress;
}

View File

@ -19,6 +19,7 @@
package com.cloud.vm.dao;
import com.cloud.utils.Pair;
import com.cloud.vm.ConsoleSessionVO;
import com.cloud.utils.db.GenericDao;
@ -36,4 +37,9 @@ public interface ConsoleSessionDao extends GenericDao<ConsoleSessionVO, Long> {
void acquireSession(String sessionUuid, String clientAddress);
int expungeByVmList(List<Long> vmIds, Long batchSize);
Pair<List<ConsoleSessionVO>, Integer> listConsoleSessions(Long id, List<Long> domainIds, Long accountId, Long userId, Long hostId,
Date startDate, Date endDate, Long instanceId,
String consoleEndpointCreatorAddress, String clientAddress,
boolean activeOnly, boolean acquired, Long pageSizeVal, Long startIndex);
}

View File

@ -22,6 +22,8 @@ package com.cloud.vm.dao;
import java.util.Date;
import java.util.List;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import org.apache.commons.collections.CollectionUtils;
import com.cloud.utils.db.GenericDaoBase;
@ -30,13 +32,28 @@ import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.ConsoleSessionVO;
public class ConsoleSessionDaoImpl extends GenericDaoBase<ConsoleSessionVO, Long> implements ConsoleSessionDao {
private static final String ID = "id";
private static final String DOMAIN_IDS = "domainIds";
private static final String ACCOUNT_ID = "accountId";
private static final String USER_ID = "userId";
private static final String HOST_ID = "hostId";
private static final String INSTANCE_ID = "instanceId";
private static final String VM_IDS = "vmIds";
private static final String START_DATE = "startDate";
private static final String END_DATE = "endDate";
private static final String CREATOR_ADDRESS = "creatorAddress";
private static final String CLIENT_ADDRESS = "clientAddress";
private static final String ACQUIRED = "acquired";
private static final String CREATED = "created";
private static final String REMOVED = "removed";
private static final String REMOVED_NOT_NULL = "removedNotNull";
private final SearchBuilder<ConsoleSessionVO> searchByRemovedDate;
public ConsoleSessionDaoImpl() {
searchByRemovedDate = createSearchBuilder();
searchByRemovedDate.and("removedNotNull", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.NNULL);
searchByRemovedDate.and("removed", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.LTEQ);
searchByRemovedDate.and(REMOVED_NOT_NULL, searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.NNULL);
searchByRemovedDate.and(REMOVED, searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.LTEQ);
}
@Override
@ -57,7 +74,7 @@ public class ConsoleSessionDaoImpl extends GenericDaoBase<ConsoleSessionVO, Long
@Override
public int expungeSessionsOlderThanDate(Date date) {
SearchCriteria<ConsoleSessionVO> searchCriteria = searchByRemovedDate.create();
searchCriteria.setParameters("removed", date);
searchCriteria.setParameters(REMOVED, date);
return expunge(searchCriteria);
}
@ -75,9 +92,66 @@ public class ConsoleSessionDaoImpl extends GenericDaoBase<ConsoleSessionVO, Long
return 0;
}
SearchBuilder<ConsoleSessionVO> sb = createSearchBuilder();
sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN);
sb.and(VM_IDS, sb.entity().getInstanceId(), SearchCriteria.Op.IN);
SearchCriteria<ConsoleSessionVO> sc = sb.create();
sc.setParameters("vmIds", vmIds.toArray());
sc.setParameters(VM_IDS, vmIds.toArray());
return batchExpunge(sc, batchSize);
}
@Override
public Pair<List<ConsoleSessionVO>, Integer> listConsoleSessions(Long id, List<Long> domainIds, Long accountId, Long userId, Long hostId,
Date startDate, Date endDate, Long instanceId,
String consoleEndpointCreatorAddress, String clientAddress,
boolean activeOnly, boolean acquired, Long pageSizeVal, Long startIndex) {
Filter filter = new Filter(ConsoleSessionVO.class, CREATED, false, startIndex, pageSizeVal);
SearchCriteria<ConsoleSessionVO> searchCriteria = createListConsoleSessionsSearchCriteria(id, domainIds, accountId, userId, hostId,
startDate, endDate, instanceId, consoleEndpointCreatorAddress, clientAddress, activeOnly, acquired);
return searchAndCount(searchCriteria, filter, true);
}
private SearchCriteria<ConsoleSessionVO> createListConsoleSessionsSearchCriteria(Long id, List<Long> domainIds, Long accountId, Long userId, Long hostId,
Date startDate, Date endDate, Long instanceId,
String consoleEndpointCreatorAddress, String clientAddress,
boolean activeOnly, boolean acquired) {
SearchCriteria<ConsoleSessionVO> searchCriteria = createListConsoleSessionsSearchBuilder(activeOnly, acquired).create();
searchCriteria.setParametersIfNotNull(ID, id);
searchCriteria.setParametersIfNotNull(DOMAIN_IDS, domainIds.toArray());
searchCriteria.setParametersIfNotNull(ACCOUNT_ID, accountId);
searchCriteria.setParametersIfNotNull(USER_ID, userId);
searchCriteria.setParametersIfNotNull(HOST_ID, hostId);
searchCriteria.setParametersIfNotNull(INSTANCE_ID, instanceId);
searchCriteria.setParametersIfNotNull(START_DATE, startDate);
searchCriteria.setParametersIfNotNull(END_DATE, endDate);
searchCriteria.setParametersIfNotNull(CREATOR_ADDRESS, consoleEndpointCreatorAddress);
searchCriteria.setParametersIfNotNull(CLIENT_ADDRESS, clientAddress);
return searchCriteria;
}
private SearchBuilder<ConsoleSessionVO> createListConsoleSessionsSearchBuilder(boolean activeOnly, boolean acquired) {
SearchBuilder<ConsoleSessionVO> searchBuilder = createSearchBuilder();
searchBuilder.and(ID, searchBuilder.entity().getId(), SearchCriteria.Op.EQ);
searchBuilder.and(DOMAIN_IDS, searchBuilder.entity().getDomainId(), SearchCriteria.Op.IN);
searchBuilder.and(ACCOUNT_ID, searchBuilder.entity().getAccountId(), SearchCriteria.Op.EQ);
searchBuilder.and(USER_ID, searchBuilder.entity().getUserId(), SearchCriteria.Op.EQ);
searchBuilder.and(HOST_ID, searchBuilder.entity().getHostId(), SearchCriteria.Op.EQ);
searchBuilder.and(INSTANCE_ID, searchBuilder.entity().getInstanceId(), SearchCriteria.Op.EQ);
searchBuilder.and(START_DATE, searchBuilder.entity().getCreated(), SearchCriteria.Op.GTEQ);
searchBuilder.and(END_DATE, searchBuilder.entity().getCreated(), SearchCriteria.Op.LTEQ);
searchBuilder.and(CREATOR_ADDRESS, searchBuilder.entity().getConsoleEndpointCreatorAddress(), SearchCriteria.Op.EQ);
searchBuilder.and(CLIENT_ADDRESS, searchBuilder.entity().getClientAddress(), SearchCriteria.Op.EQ);
if (activeOnly) {
searchBuilder.and(ACQUIRED, searchBuilder.entity().getAcquired(), SearchCriteria.Op.NNULL);
searchBuilder.and(REMOVED, searchBuilder.entity().getRemoved(), SearchCriteria.Op.NULL);
} else if (acquired) {
searchBuilder.and(ACQUIRED, searchBuilder.entity().getAcquired(), SearchCriteria.Op.NNULL);
}
searchBuilder.done();
return searchBuilder;
}
}

View File

@ -745,3 +745,12 @@ JOIN (
GROUP BY object_store_id
) buckets_quota_sum_view ON `object_store`.id = buckets_quota_sum_view.object_store_id
SET `object_store`.allocated_size = buckets_quota_sum_view.total_quota;
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'domain_id', 'bigint(20) unsigned NOT NULL');
UPDATE `cloud`.`console_session` `cs`
SET `cs`.`domain_id` = (
SELECT `acc`.`domain_id`
FROM `cloud`.`account` `acc`
WHERE `acc`.`id` = `cs`.`account_id`
);

View File

@ -87,6 +87,7 @@ import org.apache.cloudstack.api.response.ConditionResponse;
import org.apache.cloudstack.api.response.ConfigurationGroupResponse;
import org.apache.cloudstack.api.response.ConfigurationResponse;
import org.apache.cloudstack.api.response.ConfigurationSubGroupResponse;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
import org.apache.cloudstack.api.response.ControlledEntityResponse;
import org.apache.cloudstack.api.response.ControlledViewEntityResponse;
import org.apache.cloudstack.api.response.CounterResponse;
@ -205,6 +206,7 @@ import org.apache.cloudstack.backup.dao.BackupRepositoryDao;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.config.ConfigurationGroup;
import org.apache.cloudstack.config.ConfigurationSubGroup;
import org.apache.cloudstack.consoleproxy.ConsoleSession;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap;
@ -5614,4 +5616,71 @@ protected Map<String, ResourceIcon> getResourceIconsUsingOsCategory(List<Templat
return guiThemeResponse;
}
private void populateDomainFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) {
Domain domain = ApiDBUtils.findDomainById(consoleSession.getDomainId());
if (domain != null) {
consoleSessionResponse.setDomain(domain.getName());
consoleSessionResponse.setDomainPath(domain.getPath());
consoleSessionResponse.setDomainId(domain.getUuid());
}
}
private void populateUserFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) {
User user = findUserById(consoleSession.getUserId());
if (user != null) {
consoleSessionResponse.setUser(user.getUsername());
consoleSessionResponse.setUserId(user.getUuid());
}
}
private void populateAccountFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) {
Account account = ApiDBUtils.findAccountById(consoleSession.getAccountId());
if (account != null) {
consoleSessionResponse.setAccount(account.getAccountName());
consoleSessionResponse.setAccountId(account.getUuid());
}
}
private void populateHostFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) {
Host host = findHostById(consoleSession.getHostId());
if (host != null) {
consoleSessionResponse.setHostId(host.getUuid());
consoleSessionResponse.setHostName(host.getName());
}
}
private void populateInstanceFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) {
VMInstanceVO instance = ApiDBUtils.findVMInstanceById(consoleSession.getInstanceId());
if (instance != null) {
consoleSessionResponse.setVmId(instance.getUuid());
consoleSessionResponse.setVmName(instance.getInstanceName());
}
}
@Override
public ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView) {
ConsoleSessionResponse consoleSessionResponse = new ConsoleSessionResponse();
if (consoleSession == null) {
return consoleSessionResponse;
}
consoleSessionResponse.setId(consoleSession.getUuid());
consoleSessionResponse.setCreated(consoleSession.getCreated());
consoleSessionResponse.setAcquired(consoleSession.getAcquired());
consoleSessionResponse.setRemoved(consoleSession.getRemoved());
consoleSessionResponse.setConsoleEndpointCreatorAddress(consoleSession.getConsoleEndpointCreatorAddress());
consoleSessionResponse.setClientAddress(consoleSession.getClientAddress());
populateDomainFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse);
populateUserFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse);
populateAccountFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse);
populateInstanceFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse);
if (responseView == ResponseView.Full) {
populateHostFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse);
}
consoleSessionResponse.setObjectName("consolesession");
return consoleSessionResponse;
}
}

View File

@ -395,6 +395,7 @@ import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd;
import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd;
import org.apache.cloudstack.api.command.user.config.ListCapabilitiesCmd;
import org.apache.cloudstack.api.command.user.consoleproxy.CreateConsoleEndpointCmd;
import org.apache.cloudstack.api.command.user.consoleproxy.ListConsoleSessionsCmd;
import org.apache.cloudstack.api.command.user.event.ArchiveEventsCmd;
import org.apache.cloudstack.api.command.user.event.DeleteEventsCmd;
import org.apache.cloudstack.api.command.user.event.ListEventTypesCmd;
@ -4268,8 +4269,12 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(ConfigureOutOfBandManagementCmd.class);
cmdList.add(IssueOutOfBandManagementPowerActionCmd.class);
cmdList.add(ChangeOutOfBandManagementPasswordCmd.class);
cmdList.add(GetUserKeysCmd.class);
// Console Session APIs
cmdList.add(CreateConsoleEndpointCmd.class);
cmdList.add(ListConsoleSessionsCmd.class);
//user data APIs
cmdList.add(RegisterUserDataCmd.class);

View File

@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.consoleproxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@ -27,7 +28,15 @@ import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.domain.Domain;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint;
import org.apache.cloudstack.api.command.user.consoleproxy.ListConsoleSessionsCmd;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.security.keys.KeysManager;
import org.apache.commons.codec.binary.Base64;
@ -86,6 +95,8 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
@Inject
private AccountManager accountManager;
@Inject
private DomainDao domainDao;
@Inject
private VirtualMachineManager virtualMachineManager;
@Inject
private ManagementServer managementServer;
@ -103,6 +114,8 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
DataCenterDao dataCenterDao;
@Inject
private ConsoleSessionDao consoleSessionDao;
@Inject
private ResponseGenerator responseGenerator;
private ScheduledExecutorService executorService = null;
@ -181,6 +194,78 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
}
}
@Override
public ListResponse<ConsoleSessionResponse> listConsoleSessions(ListConsoleSessionsCmd cmd) {
Pair<List<ConsoleSessionVO>, Integer> consoleSessions = listConsoleSessionsInternal(cmd);
ListResponse<ConsoleSessionResponse> response = new ListResponse<>();
ResponseObject.ResponseView responseView = ResponseObject.ResponseView.Restricted;
Long callerId = CallContext.current().getCallingAccountId();
if (accountManager.isRootAdmin(callerId)) {
responseView = ResponseObject.ResponseView.Full;
}
List<ConsoleSessionResponse> consoleSessionResponses = new ArrayList<>();
for (ConsoleSessionVO consoleSession : consoleSessions.first()) {
ConsoleSessionResponse consoleSessionResponse = responseGenerator.createConsoleSessionResponse(consoleSession, responseView);
consoleSessionResponses.add(consoleSessionResponse);
}
response.setResponses(consoleSessionResponses, consoleSessions.second());
return response;
}
protected Pair<List<ConsoleSessionVO>, Integer> listConsoleSessionsInternal(ListConsoleSessionsCmd cmd) {
CallContext caller = CallContext.current();
long domainId = getBaseDomainIdToListConsoleSessions(cmd.getDomainId());
Long accountId = cmd.getAccountId();
Long userId = cmd.getUserId();
boolean isRecursive = cmd.isRecursive();
boolean isCallerNormalUser = accountManager.isNormalUser(caller.getCallingAccountId());
if (isCallerNormalUser) {
accountId = caller.getCallingAccountId();
userId = caller.getCallingUserId();
}
List<Long> domainIds = isRecursive ? domainDao.getDomainAndChildrenIds(domainId) : List.of(domainId);
return consoleSessionDao.listConsoleSessions(cmd.getId(), domainIds, accountId, userId,
cmd.getHostId(), cmd.getStartDate(), cmd.getEndDate(), cmd.getVmId(),
cmd.getConsoleEndpointCreatorAddress(), cmd.getClientAddress(), cmd.isActiveOnly(),
cmd.getAcquired(), cmd.getPageSizeVal(), cmd.getStartIndex());
}
/**
* Determines the base domain ID for listing console sessions.
*
* If no domain ID is provided, returns the caller's domain ID. Otherwise,
* checks if the caller has access to that domain and returns the provided domain ID.
*
* @param domainId The domain ID to check, can be null
* @return The base domain ID to use for listing console sessions
* @throws PermissionDeniedException if the caller does not have access to the specified domain
*/
protected long getBaseDomainIdToListConsoleSessions(Long domainId) {
Account caller = CallContext.current().getCallingAccount();
if (domainId == null) {
return caller.getDomainId();
}
Domain domain = domainDao.findById(domainId);
if (domain == null) {
throw new InvalidParameterValueException(String.format("Unable to find domain with ID [%s]. Verify the informed domain and try again.", domainId));
}
accountManager.checkAccess(caller, domain);
return domainId;
}
@Override
public ConsoleSession listConsoleSessionById(long id) {
return consoleSessionDao.findByIdIncludingRemoved(id);
}
@Override
public ConsoleEndpoint generateConsoleEndpoint(Long vmId, String extraSecurityToken, String clientAddress) {
try {
@ -411,10 +496,13 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
}
protected void persistConsoleSession(String sessionUuid, long instanceId, long hostId, String consoleEndpointCreatorAddress) {
CallContext caller = CallContext.current();
ConsoleSessionVO consoleSessionVo = new ConsoleSessionVO();
consoleSessionVo.setUuid(sessionUuid);
consoleSessionVo.setAccountId(CallContext.current().getCallingAccountId());
consoleSessionVo.setUserId(CallContext.current().getCallingUserId());
consoleSessionVo.setDomainId(caller.getCallingAccount().getDomainId());
consoleSessionVo.setAccountId(caller.getCallingAccountId());
consoleSessionVo.setUserId(caller.getCallingUserId());
consoleSessionVo.setInstanceId(instanceId);
consoleSessionVo.setHostId(hostId);
consoleSessionVo.setConsoleEndpointCreatorAddress(consoleEndpointCreatorAddress);

View File

@ -67,6 +67,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.capacity.Capacity;
import com.cloud.configuration.Resource;
import com.cloud.domain.DomainVO;
import com.cloud.host.HostVO;
import com.cloud.network.PublicIpQuarantine;
import com.cloud.network.as.AutoScaleVmGroup;
import com.cloud.network.as.AutoScaleVmGroupVO;
@ -93,7 +94,11 @@ import com.cloud.user.UserDataVO;
import com.cloud.user.UserVO;
import com.cloud.user.dao.UserDataDao;
import com.cloud.utils.net.Ip;
import com.cloud.vm.ConsoleSessionVO;
import com.cloud.vm.NicSecondaryIp;
import com.cloud.vm.VMInstanceVO;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
@RunWith(MockitoJUnitRunner.class)
public class ApiResponseHelperTest {
@ -124,6 +129,19 @@ public class ApiResponseHelperTest {
@Mock
ResourceIconManager resourceIconManager;
@Mock
private ConsoleSessionVO consoleSessionMock;
@Mock
private DomainVO domainVOMock;
@Mock
private UserVO userVOMock;
@Mock
private AccountVO accountVOMock;
@Mock
private HostVO hostVOMock;
@Mock
private VMInstanceVO vmInstanceVOMock;
@Spy
@InjectMocks
ApiResponseHelper apiResponseHelper = new ApiResponseHelper();
@ -631,4 +649,114 @@ public class ApiResponseHelperTest {
Mockito.verify(resourceIconManager, Mockito.never()).getByResourceTypeAndUuids(Mockito.any(),
Mockito.anyCollection());
}
private ConsoleSessionResponse getExpectedConsoleSessionResponseForTests(boolean fullView) {
ConsoleSessionResponse expected = new ConsoleSessionResponse();
expected.setId("uuid");
expected.setCreated(new Date());
expected.setAcquired(new Date());
expected.setRemoved(new Date());
expected.setConsoleEndpointCreatorAddress("127.0.0.1");
expected.setClientAddress("127.0.0.1");
if (fullView) {
expected.setDomain("domain");
expected.setDomainPath("domainPath");
expected.setDomainId("domainUuid");
expected.setUser("user");
expected.setUserId("userUuid");
expected.setAccount("account");
expected.setAccountId("accountUuid");
expected.setHostName("host");
expected.setHostId("hostUuid");
expected.setVmId("vmUuid");
expected.setVmName("vmName");
}
return expected;
}
@Test
public void createConsoleSessionResponseTestShouldReturnRestrictedResponse() {
ConsoleSessionResponse expected = getExpectedConsoleSessionResponseForTests(false);
try (MockedStatic<ApiDBUtils> apiDBUtilsStaticMock = Mockito.mockStatic(ApiDBUtils.class)) {
Mockito.when(consoleSessionMock.getUuid()).thenReturn(expected.getId());
Mockito.when(consoleSessionMock.getDomainId()).thenReturn(2L);
Mockito.when(consoleSessionMock.getCreated()).thenReturn(expected.getCreated());
Mockito.when(consoleSessionMock.getAcquired()).thenReturn(expected.getAcquired());
Mockito.when(consoleSessionMock.getRemoved()).thenReturn(expected.getRemoved());
Mockito.when(consoleSessionMock.getConsoleEndpointCreatorAddress()).thenReturn(expected.getConsoleEndpointCreatorAddress());
Mockito.when(consoleSessionMock.getClientAddress()).thenReturn(expected.getClientAddress());
ConsoleSessionResponse response = apiResponseHelper.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Restricted);
Assert.assertEquals(expected.getId(), response.getId());
Assert.assertEquals(expected.getCreated(), response.getCreated());
Assert.assertEquals(expected.getAcquired(), response.getAcquired());
Assert.assertEquals(expected.getRemoved(), response.getRemoved());
Assert.assertEquals(expected.getConsoleEndpointCreatorAddress(), response.getConsoleEndpointCreatorAddress());
Assert.assertEquals(expected.getClientAddress(), response.getClientAddress());
}
}
@Test
public void createConsoleSessionResponseTestShouldReturnFullResponse() {
ConsoleSessionResponse expected = getExpectedConsoleSessionResponseForTests(true);
try (MockedStatic<ApiDBUtils> apiDBUtilsStaticMock = Mockito.mockStatic(ApiDBUtils.class)) {
Mockito.when(consoleSessionMock.getUuid()).thenReturn(expected.getId());
Mockito.when(consoleSessionMock.getDomainId()).thenReturn(2L);
Mockito.when(consoleSessionMock.getAccountId()).thenReturn(2L);
Mockito.when(consoleSessionMock.getUserId()).thenReturn(2L);
Mockito.when(consoleSessionMock.getHostId()).thenReturn(2L);
Mockito.when(consoleSessionMock.getInstanceId()).thenReturn(2L);
Mockito.when(consoleSessionMock.getCreated()).thenReturn(expected.getCreated());
Mockito.when(consoleSessionMock.getAcquired()).thenReturn(expected.getAcquired());
Mockito.when(consoleSessionMock.getRemoved()).thenReturn(expected.getRemoved());
Mockito.when(consoleSessionMock.getConsoleEndpointCreatorAddress()).thenReturn(expected.getConsoleEndpointCreatorAddress());
Mockito.when(consoleSessionMock.getClientAddress()).thenReturn(expected.getClientAddress());
apiDBUtilsStaticMock.when(() -> ApiDBUtils.findDomainById(2L)).thenReturn(domainVOMock);
Mockito.when(domainVOMock.getName()).thenReturn(expected.getDomain());
Mockito.when(domainVOMock.getPath()).thenReturn(expected.getDomainPath());
Mockito.when(domainVOMock.getUuid()).thenReturn(expected.getDomainId());
Mockito.when(apiResponseHelper.findUserById(2L)).thenReturn(userVOMock);
Mockito.when(userVOMock.getUsername()).thenReturn(expected.getUser());
Mockito.when(userVOMock.getUuid()).thenReturn(expected.getUserId());
Mockito.when(ApiDBUtils.findAccountById(2L)).thenReturn(accountVOMock);
Mockito.when(accountVOMock.getAccountName()).thenReturn(expected.getAccount());
Mockito.when(accountVOMock.getUuid()).thenReturn(expected.getAccountId());
Mockito.when(apiResponseHelper.findHostById(2L)).thenReturn(hostVOMock);
Mockito.when(hostVOMock.getUuid()).thenReturn(expected.getHostId());
Mockito.when(hostVOMock.getName()).thenReturn(expected.getHostName());
apiDBUtilsStaticMock.when(() -> ApiDBUtils.findVMInstanceById(2L)).thenReturn(vmInstanceVOMock);
Mockito.when(vmInstanceVOMock.getUuid()).thenReturn(expected.getVmId());
Mockito.when(vmInstanceVOMock.getInstanceName()).thenReturn(expected.getVmName());
ConsoleSessionResponse response = apiResponseHelper.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Full);
Assert.assertEquals(expected.getId(), response.getId());
Assert.assertEquals(expected.getCreated(), response.getCreated());
Assert.assertEquals(expected.getAcquired(), response.getAcquired());
Assert.assertEquals(expected.getRemoved(), response.getRemoved());
Assert.assertEquals(expected.getConsoleEndpointCreatorAddress(), response.getConsoleEndpointCreatorAddress());
Assert.assertEquals(expected.getClientAddress(), response.getClientAddress());
Assert.assertEquals(expected.getDomain(), response.getDomain());
Assert.assertEquals(expected.getDomainPath(), response.getDomainPath());
Assert.assertEquals(expected.getDomainId(), response.getDomainId());
Assert.assertEquals(expected.getUser(), response.getUser());
Assert.assertEquals(expected.getUserId(), response.getUserId());
Assert.assertEquals(expected.getAccount(), response.getAccount());
Assert.assertEquals(expected.getAccountId(), response.getAccountId());
Assert.assertEquals(expected.getHostId(), response.getHostId());
Assert.assertEquals(expected.getHostName(), response.getHostName());
Assert.assertEquals(expected.getVmId(), response.getVmId());
Assert.assertEquals(expected.getVmName(), response.getVmName());
}
}
}

View File

@ -17,21 +17,32 @@
package org.apache.cloudstack.consoleproxy;
import com.cloud.agent.AgentManager;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.server.ManagementServer;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.Pair;
import com.cloud.utils.db.EntityManager;
import com.cloud.vm.ConsoleSessionVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.dao.ConsoleSessionDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.command.user.consoleproxy.ListConsoleSessionsCmd;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.security.keys.KeysManager;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@ -66,6 +77,23 @@ public class ConsoleAccessManagerImplTest {
@Mock
Account account;
@Mock
private CallContext callContextMock;
@Mock
private DomainDao domainDaoMock;
@Mock
private DomainVO domainMock;
@Mock
private ConsoleSessionVO consoleSessionMock;
@Mock
private ConsoleSessionDao consoleSessionDaoMock;
@Mock
private ConsoleSessionResponse consoleSessionResponseMock;
@Mock
private ListConsoleSessionsCmd listConsoleSessionsCmdMock;
@Mock
private ResponseGenerator responseGeneratorMock;
@Test
public void testCheckSessionPermissionAdminAccount() {
Mockito.when(account.getId()).thenReturn(1L);
@ -106,4 +134,181 @@ public class ConsoleAccessManagerImplTest {
Assert.assertFalse(consoleAccessManager.checkSessionPermission(virtualMachine, account));
}
}
@Test
public void listConsoleSessionsInternalTestNormalUsersShouldOnlyBeAllowedToListTheirOwnConsoleSessions() {
long callerDomainId = 5L;
long callerAccountId = 5L;
long callerUserId = 5L;
boolean isRecursive = false;
try (MockedStatic<CallContext> callContextStaticMock = Mockito.mockStatic(CallContext.class)) {
callContextStaticMock.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(null);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(account);
Mockito.when(account.getDomainId()).thenReturn(callerDomainId);
Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive);
Mockito.when(accountManager.isNormalUser(callerAccountId)).thenReturn(true);
Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callerAccountId);
Mockito.when(callContextMock.getCallingUserId()).thenReturn(callerUserId);
consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock);
}
Mockito.verify(consoleSessionDaoMock).listConsoleSessions(
Mockito.any(), Mockito.eq(List.of(callerDomainId)), Mockito.eq(callerAccountId),
Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.any(), Mockito.any()
);
}
@Test
public void listConsoleSessionsInternalTestAdminsShouldBeAllowedToRetrieveOtherAccountsConsoleSessions() {
long callerDomainId = 5L;
long callerAccountId = 5L;
long callerUserId = 5L;
boolean isRecursive = false;
try (MockedStatic<CallContext> callContextStaticMock = Mockito.mockStatic(CallContext.class)) {
callContextStaticMock.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(callerDomainId);
Mockito.doReturn(callerDomainId).when(consoleAccessManager).getBaseDomainIdToListConsoleSessions(callerDomainId);
Mockito.when(listConsoleSessionsCmdMock.getAccountId()).thenReturn(callerAccountId);
Mockito.when(listConsoleSessionsCmdMock.getUserId()).thenReturn(callerUserId);
Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive);
Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callerAccountId);
Mockito.when(accountManager.isNormalUser(callerAccountId)).thenReturn(false);
consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock);
}
Mockito.verify(consoleSessionDaoMock).listConsoleSessions(
Mockito.any(), Mockito.eq(List.of(callerDomainId)), Mockito.eq(callerAccountId),
Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.any(), Mockito.any()
);
}
@Test
public void listConsoleSessionsInternalTestShouldNotFetchConsoleSessionsRecursivelyWhenIsRecursiveIsFalse() {
long callerDomainId = 5L;
long callerAccountId = 5L;
long callerUserId = 5L;
boolean isRecursive = false;
try (MockedStatic<CallContext> callContextStaticMock = Mockito.mockStatic(CallContext.class)) {
callContextStaticMock.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(callerDomainId);
Mockito.doReturn(callerDomainId).when(consoleAccessManager).getBaseDomainIdToListConsoleSessions(callerDomainId);
Mockito.when(listConsoleSessionsCmdMock.getAccountId()).thenReturn(callerAccountId);
Mockito.when(listConsoleSessionsCmdMock.getUserId()).thenReturn(callerUserId);
Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive);
Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callerAccountId);
Mockito.when(accountManager.isNormalUser(callerAccountId)).thenReturn(false);
consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock);
}
Mockito.verify(consoleSessionDaoMock).listConsoleSessions(
Mockito.any(), Mockito.eq(List.of(callerDomainId)), Mockito.eq(callerAccountId),
Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.any(), Mockito.any()
);
}
@Test
public void listConsoleSessionsInternalTestShouldFetchConsoleSessionsRecursivelyWhenIsRecursiveIsTrue() {
long callerDomainId = 5L;
long callerAccountId = 5L;
long callerUserId = 5L;
boolean isRecursive = true;
List<Long> domainIdsCallerHasAccessTo = List.of(callerDomainId, 6L, 7L);
try (MockedStatic<CallContext> callContextStaticMock = Mockito.mockStatic(CallContext.class)) {
callContextStaticMock.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(callerDomainId);
Mockito.doReturn(callerDomainId).when(consoleAccessManager).getBaseDomainIdToListConsoleSessions(callerDomainId);
Mockito.when(listConsoleSessionsCmdMock.getAccountId()).thenReturn(callerAccountId);
Mockito.when(listConsoleSessionsCmdMock.getUserId()).thenReturn(callerUserId);
Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive);
Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callerAccountId);
Mockito.when(accountManager.isNormalUser(callerAccountId)).thenReturn(false);
Mockito.when(domainDaoMock.getDomainAndChildrenIds(callerDomainId)).thenReturn(domainIdsCallerHasAccessTo);
consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock);
}
Mockito.verify(consoleSessionDaoMock).listConsoleSessions(
Mockito.any(), Mockito.eq(domainIdsCallerHasAccessTo), Mockito.eq(callerAccountId),
Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.any(), Mockito.any()
);
}
@Test
public void listConsoleSessionsTestShouldCreateResponsesWithFullViewForRootAdmins() {
Mockito.doReturn(new Pair<>(List.of(consoleSessionMock), 1))
.when(consoleAccessManager)
.listConsoleSessionsInternal(listConsoleSessionsCmdMock);
try (MockedStatic<CallContext> callContextStaticMock = Mockito.mockStatic(CallContext.class)) {
callContextStaticMock.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccountId()).thenReturn(2L);
Mockito.when(accountManager.isRootAdmin(2L)).thenReturn(true);
Mockito.when(responseGeneratorMock.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Full)).thenReturn(consoleSessionResponseMock);
consoleAccessManager.listConsoleSessions(listConsoleSessionsCmdMock);
}
Mockito.verify(responseGeneratorMock).createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Full);
}
@Test
public void listConsoleSessionsTestShouldCreateResponsesWithRestrictedViewForNonRootAdmins() {
Mockito.doReturn(new Pair<>(List.of(consoleSessionMock), 1))
.when(consoleAccessManager)
.listConsoleSessionsInternal(listConsoleSessionsCmdMock);
try (MockedStatic<CallContext> callContextStaticMock = Mockito.mockStatic(CallContext.class)) {
callContextStaticMock.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccountId()).thenReturn(2L);
Mockito.when(accountManager.isRootAdmin(2L)).thenReturn(false);
Mockito.when(responseGeneratorMock.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Restricted)).thenReturn(consoleSessionResponseMock);
consoleAccessManager.listConsoleSessions(listConsoleSessionsCmdMock);
}
Mockito.verify(responseGeneratorMock).createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Restricted);
}
@Test
public void getBaseDomainIdToListConsoleSessionsTestIfNoDomainIdIsProvidedReturnCallersDomainId() {
long callerDomainId = 5L;
try (MockedStatic<CallContext> callContextStaticMock = Mockito.mockStatic(CallContext.class)) {
callContextStaticMock.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(account);
Mockito.when(account.getDomainId()).thenReturn(callerDomainId);
Assert.assertEquals(callerDomainId, consoleAccessManager.getBaseDomainIdToListConsoleSessions(null));
}
}
@Test
public void getBaseDomainIdToListConsoleSessionsTestPerformAccessValidationWhenDomainIsProvided() {
long domainId = 5L;
try (MockedStatic<CallContext> callContextStaticMock = Mockito.mockStatic(CallContext.class)) {
callContextStaticMock.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(account);
Mockito.when(domainDaoMock.findById(domainId)).thenReturn(domainMock);
Assert.assertEquals(domainId, consoleAccessManager.getBaseDomainIdToListConsoleSessions(domainId));
Mockito.verify(accountManager).checkAccess(account, domainMock);
}
}
@Test
public void listConsoleSessionByIdTestShouldCallDbLayer() {
consoleAccessManager.listConsoleSessionById(1L);
Mockito.verify(consoleSessionDaoMock).findByIdIncludingRemoved(1L);
}
}

View File

@ -228,7 +228,8 @@ known_categories = {
'Rolling': 'Rolling Maintenance',
'importVsphereStoragePolicies' : 'vSphere storage policies',
'listVsphereStoragePolicies' : 'vSphere storage policies',
'ConsoleEndpoint': 'Console Endpoint',
'createConsoleEndpoint': 'Console Session',
'listConsoleSessions': 'Console Session',
'importVm': 'Virtual Machine',
'revertToVMSnapshot': 'Virtual Machine',
'listQuarantinedIp': 'IP Quarantine',