🎉 优化 asset 模块逻辑.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.api;
|
||||
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 资产模块 授权数据对外服务
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/10/12 16:13
|
||||
*/
|
||||
public interface AssetAuthorizedDataApi {
|
||||
|
||||
/**
|
||||
* 获取用户已授权&配置已启用的主机id 查询角色
|
||||
*
|
||||
* @param userId userId
|
||||
* @param type type
|
||||
* @return hostId
|
||||
*/
|
||||
List<Long> getUserAuthorizedEnabledHostId(Long userId, HostTypeEnum type);
|
||||
|
||||
/**
|
||||
* 查询用户已授权并且启用的主机
|
||||
*
|
||||
* @param userId userId
|
||||
* @param type type
|
||||
* @return group
|
||||
*/
|
||||
List<HostDTO> getUserAuthorizedHostList(Long userId, HostTypeEnum type);
|
||||
|
||||
}
|
||||
@@ -20,26 +20,35 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.asset.handler.host.terminal.handler;
|
||||
package org.dromara.visor.module.asset.api;
|
||||
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.TerminalBasePayload;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 终端消息处理器
|
||||
* 主机 对外服务
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/12/29 18:53
|
||||
* @since 2024/10/12 16:14
|
||||
*/
|
||||
public interface ITerminalHandler<T extends TerminalBasePayload> {
|
||||
public interface HostApi {
|
||||
|
||||
/**
|
||||
* 处理消息
|
||||
* 通过 id 查询
|
||||
*
|
||||
* @param channel channel
|
||||
* @param payload payload
|
||||
* @param id id
|
||||
* @return row
|
||||
*/
|
||||
void handle(WebSocketSession channel, T payload);
|
||||
HostDTO selectById(Long id);
|
||||
|
||||
/**
|
||||
* 通过 id 查询
|
||||
*
|
||||
* @param idList idList
|
||||
* @return rows
|
||||
*/
|
||||
List<HostDTO> selectByIdList(List<Long> idList);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.api;
|
||||
|
||||
import org.dromara.visor.common.session.config.RdpConnectConfig;
|
||||
import org.dromara.visor.common.session.config.SshConnectConfig;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
|
||||
|
||||
/**
|
||||
* 主机连接 对外服务
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/10/12 23:53
|
||||
*/
|
||||
public interface HostConnectApi {
|
||||
|
||||
/**
|
||||
* 获取 SSH 连接配置
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @return session
|
||||
*/
|
||||
SshConnectConfig getSshConnectConfig(Long hostId);
|
||||
|
||||
/**
|
||||
* 使用用户配置获取 SSH 连接配置
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @param userId userId
|
||||
* @return session
|
||||
*/
|
||||
SshConnectConfig getSshConnectConfig(Long hostId, Long userId);
|
||||
|
||||
/**
|
||||
* 使用用户配置获取 SSH 连接配置
|
||||
*
|
||||
* @param host host
|
||||
* @param userId userId
|
||||
* @return session
|
||||
*/
|
||||
SshConnectConfig getSshConnectConfig(HostDTO host, Long userId);
|
||||
|
||||
/**
|
||||
* 获取 RDP 连接配置
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @return session
|
||||
*/
|
||||
RdpConnectConfig getRdpConnectConfig(Long hostId);
|
||||
|
||||
/**
|
||||
* 使用用户配置获取 RDP 连接配置
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @param userId userId
|
||||
* @return session
|
||||
*/
|
||||
RdpConnectConfig getRdpConnectConfig(Long hostId, Long userId);
|
||||
|
||||
/**
|
||||
* 使用用户配置获取 RDP 连接配置
|
||||
*
|
||||
* @param host host
|
||||
* @param userId userId
|
||||
* @return session
|
||||
*/
|
||||
RdpConnectConfig getRdpConnectConfig(HostDTO host, Long userId);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.entity.dto.host;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 主机基本信息 业务响应对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023-9-11 14:16
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "HostBaseDTO", description = "主机基本信息 业务响应对象")
|
||||
public class HostBaseDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "id")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "主机类型")
|
||||
private String types;
|
||||
|
||||
@Schema(description = "系统类型")
|
||||
private String osType;
|
||||
|
||||
@Schema(description = "系统架构")
|
||||
private String archType;
|
||||
|
||||
@Schema(description = "主机名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "主机编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "主机地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "主机端口")
|
||||
private Integer port;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.entity.dto.host;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 主机 业务对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023-9-11 14:16
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "HostDTO", description = "主机 业务对象")
|
||||
public class HostDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "id")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "主机类型")
|
||||
private String types;
|
||||
|
||||
@Schema(description = "系统类型")
|
||||
private String osType;
|
||||
|
||||
@Schema(description = "系统架构")
|
||||
private String archType;
|
||||
|
||||
@Schema(description = "主机名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "主机编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "主机地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "主机端口")
|
||||
private Integer port;
|
||||
|
||||
@Schema(description = "主机状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "修改时间")
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "修改人")
|
||||
private String updater;
|
||||
|
||||
@Schema(description = "是否收藏")
|
||||
private Boolean favorite;
|
||||
|
||||
@Schema(description = "分组 id")
|
||||
private Set<Long> groupIdList;
|
||||
|
||||
@Schema(description = "别名")
|
||||
private String alias;
|
||||
|
||||
@Schema(description = "颜色")
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 转为 base
|
||||
*
|
||||
* @return base
|
||||
*/
|
||||
public HostBaseDTO toBase() {
|
||||
return HostBaseDTO.builder()
|
||||
.id(this.id)
|
||||
.types(this.types)
|
||||
.name(this.name)
|
||||
.code(this.code)
|
||||
.address(this.address)
|
||||
.port(this.port)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.entity.dto.host;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
import org.dromara.visor.common.security.UpdatePasswordAction;
|
||||
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
/**
|
||||
* 主机 RDP 配置
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/9/13 16:18
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "HostRdpConfigDTO", description = "主机 RDP 配置业务对象")
|
||||
public class HostRdpConfigDTO implements GenericsDataModel, UpdatePasswordAction {
|
||||
|
||||
@NotNull
|
||||
@Min(value = 1)
|
||||
@Max(value = 65535)
|
||||
@Schema(description = "主机端口")
|
||||
private Integer port;
|
||||
|
||||
@Size(max = 128)
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 12)
|
||||
@Schema(description = "认证方式")
|
||||
private String authType;
|
||||
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "身份id")
|
||||
private Long identityId;
|
||||
|
||||
@Schema(description = "RDP 版本是否大于8.1")
|
||||
private Boolean versionGt81;
|
||||
|
||||
@Schema(description = "时区")
|
||||
private String timezone;
|
||||
|
||||
@Schema(description = "键盘布局")
|
||||
private String keyboardLayout;
|
||||
|
||||
@Schema(description = "剪切板规范")
|
||||
private String clipboardNormalize;
|
||||
|
||||
@Schema(description = "域")
|
||||
private String domain;
|
||||
|
||||
@Schema(description = "预连接id")
|
||||
private String preConnectionId;
|
||||
|
||||
@Schema(description = "预连接数据")
|
||||
private String preConnectionBlob;
|
||||
|
||||
@Schema(description = "远程应用")
|
||||
private String remoteApp;
|
||||
|
||||
@Schema(description = "远程应用路径")
|
||||
private String remoteAppDir;
|
||||
|
||||
@Schema(description = "远程应用参数")
|
||||
private String remoteAppArgs;
|
||||
|
||||
@Schema(description = "是否使用新密码 仅参数")
|
||||
private Boolean useNewPassword;
|
||||
|
||||
@Schema(description = "是否已设置密码 仅返回")
|
||||
private Boolean hasPassword;
|
||||
|
||||
}
|
||||
@@ -20,8 +20,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.asset.handler.host.config.model;
|
||||
package org.dromara.visor.module.asset.entity.dto.host;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@@ -42,81 +43,58 @@ import javax.validation.constraints.*;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HostSshConfigModel implements GenericsDataModel, UpdatePasswordAction {
|
||||
@Schema(name = "HostSshConfigDTO", description = "主机 SSH 配置业务对象")
|
||||
public class HostSshConfigDTO implements GenericsDataModel, UpdatePasswordAction {
|
||||
|
||||
/**
|
||||
* 主机端口
|
||||
*/
|
||||
@NotNull
|
||||
@Min(value = 1)
|
||||
@Max(value = 65535)
|
||||
@Schema(description = "主机端口")
|
||||
private Integer port;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@Size(max = 128)
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 认证方式
|
||||
*/
|
||||
@NotBlank
|
||||
@Size(max = 12)
|
||||
@Schema(description = "认证方式")
|
||||
private String authType;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 身份id
|
||||
*/
|
||||
@Schema(description = "身份id")
|
||||
private Long identityId;
|
||||
|
||||
/**
|
||||
* 密钥id
|
||||
*/
|
||||
@Schema(description = "密钥id")
|
||||
private Long keyId;
|
||||
|
||||
/**
|
||||
* 连接超时时间
|
||||
*/
|
||||
@NotNull
|
||||
@Min(value = 1)
|
||||
@Max(value = 100000)
|
||||
@Schema(description = "连接超时时间")
|
||||
private Integer connectTimeout;
|
||||
|
||||
/**
|
||||
* SSH输出编码
|
||||
*/
|
||||
@NotBlank
|
||||
@Size(max = 12)
|
||||
@Schema(description = "SSH输出编码")
|
||||
private String charset;
|
||||
|
||||
/**
|
||||
* 文件名称编码
|
||||
*/
|
||||
@NotBlank
|
||||
@Size(max = 12)
|
||||
@Schema(description = "文件名称编码")
|
||||
private String fileNameCharset;
|
||||
|
||||
/**
|
||||
* 文件内容编码
|
||||
*/
|
||||
@NotBlank
|
||||
@Size(max = 12)
|
||||
@Schema(description = "文件内容编码")
|
||||
private String fileContentCharset;
|
||||
|
||||
/**
|
||||
* 是否使用新密码 仅参数
|
||||
*/
|
||||
@Schema(description = "是否使用新密码 仅参数")
|
||||
private Boolean useNewPassword;
|
||||
|
||||
/**
|
||||
* 是否已设置密码 仅返回
|
||||
*/
|
||||
@Schema(description = "是否已设置密码 仅返回")
|
||||
private Boolean hasPassword;
|
||||
|
||||
}
|
||||
@@ -22,13 +22,11 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.enums;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.handler.data.GenericsStrategyDefinition;
|
||||
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
import org.dromara.visor.common.handler.data.strategy.GenericsDataStrategy;
|
||||
import org.dromara.visor.module.asset.handler.host.config.strategy.HostSshConfigStrategy;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostRdpConfigDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostSshConfigDTO;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -37,24 +35,28 @@ import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 主机配置类型枚举
|
||||
* 主机类型
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/9/11 14:37
|
||||
* @since 2024/10/12 18:12
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum HostTypeEnum implements GenericsStrategyDefinition {
|
||||
public enum HostTypeEnum {
|
||||
|
||||
/**
|
||||
* SSH
|
||||
*/
|
||||
SSH(HostSshConfigStrategy.class),
|
||||
SSH(HostSshConfigDTO.class),
|
||||
|
||||
/**
|
||||
* RDP
|
||||
*/
|
||||
RDP(HostRdpConfigDTO.class),
|
||||
|
||||
;
|
||||
|
||||
private final Class<? extends GenericsDataStrategy<? extends GenericsDataModel>> strategyClass;
|
||||
private final Class<?> clazz;
|
||||
|
||||
public static HostTypeEnum of(String type) {
|
||||
if (type == null) {
|
||||
@@ -79,4 +81,9 @@ public enum HostTypeEnum implements GenericsStrategyDefinition {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T parse(String config) {
|
||||
return (T) JSON.parseObject(config, this.clazz);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,11 @@
|
||||
<artifactId>orion-visor-module-asset-provider</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.visor</groupId>
|
||||
<artifactId>orion-visor-module-exec-provider</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- framework starter -->
|
||||
<dependency>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.api.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.module.asset.api.AssetAuthorizedDataApi;
|
||||
import org.dromara.visor.module.asset.convert.HostProviderConvert;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
|
||||
import org.dromara.visor.module.asset.entity.vo.AuthorizedHostWrapperVO;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 资产模块 授权数据对外服务实现
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/10/12 18:14
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AssetAuthorizedDataApiImpl implements AssetAuthorizedDataApi {
|
||||
|
||||
@Resource
|
||||
private AssetAuthorizedDataService assetAuthorizedDataService;
|
||||
|
||||
@Override
|
||||
public List<Long> getUserAuthorizedEnabledHostId(Long userId, HostTypeEnum type) {
|
||||
return assetAuthorizedDataService.getUserAuthorizedEnabledHostId(userId, type.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HostDTO> getUserAuthorizedHostList(Long userId, HostTypeEnum type) {
|
||||
AuthorizedHostWrapperVO wrapper = assetAuthorizedDataService.getUserAuthorizedHost(userId, type.name());
|
||||
return wrapper.getHostList()
|
||||
.stream()
|
||||
.map(HostProviderConvert.MAPPER::to)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.api.impl;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.module.asset.api.HostApi;
|
||||
import org.dromara.visor.module.asset.convert.HostProviderConvert;
|
||||
import org.dromara.visor.module.asset.dao.HostDAO;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 主机 对外服务实现类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/10/12 18:27
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class HostApiImpl implements HostApi {
|
||||
|
||||
@Resource
|
||||
private HostDAO hostDAO;
|
||||
|
||||
@Override
|
||||
public HostDTO selectById(Long id) {
|
||||
return HostProviderConvert.MAPPER.to(hostDAO.selectById(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HostDTO> selectByIdList(List<Long> idList) {
|
||||
if (Lists.isEmpty(idList)) {
|
||||
return Lists.empty();
|
||||
}
|
||||
return hostDAO.selectBatchIds(idList)
|
||||
.stream()
|
||||
.map(HostProviderConvert.MAPPER::to)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.api.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.session.config.RdpConnectConfig;
|
||||
import org.dromara.visor.common.session.config.SshConnectConfig;
|
||||
import org.dromara.visor.module.asset.api.HostConnectApi;
|
||||
import org.dromara.visor.module.asset.convert.HostProviderConvert;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
|
||||
import org.dromara.visor.module.asset.service.HostConnectService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 主机连接 对外服务实现
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/10/13 0:03
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class HostConnectApiImpl implements HostConnectApi {
|
||||
|
||||
@Resource
|
||||
private HostConnectService hostConnectService;
|
||||
|
||||
@Override
|
||||
public SshConnectConfig getSshConnectConfig(Long hostId) {
|
||||
return hostConnectService.getSshConnectConfig(hostId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SshConnectConfig getSshConnectConfig(Long hostId, Long userId) {
|
||||
return hostConnectService.getSshConnectConfig(hostId, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SshConnectConfig getSshConnectConfig(HostDTO host, Long userId) {
|
||||
return hostConnectService.getSshConnectConfig(HostProviderConvert.MAPPER.to(host), userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RdpConnectConfig getRdpConnectConfig(Long hostId) {
|
||||
return hostConnectService.getRdpConnectConfig(hostId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RdpConnectConfig getRdpConnectConfig(Long hostId, Long userId) {
|
||||
return hostConnectService.getRdpConnectConfig(hostId, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RdpConnectConfig getRdpConnectConfig(HostDTO host, Long userId) {
|
||||
return hostConnectService.getRdpConnectConfig(HostProviderConvert.MAPPER.to(host), userId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,11 +25,12 @@ package org.dromara.visor.module.asset.controller;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.entity.chart.PieChartData;
|
||||
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
|
||||
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
|
||||
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
|
||||
import org.dromara.visor.module.asset.entity.vo.AssetWorkplaceStatisticsVO;
|
||||
import org.dromara.visor.module.asset.service.AssetStatisticsService;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -42,7 +43,7 @@ import javax.annotation.Resource;
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/12/23 16:07
|
||||
* @since 2024/12/23 15:56
|
||||
*/
|
||||
@Tag(name = "asset - 统计服务")
|
||||
@Slf4j
|
||||
@@ -56,10 +57,11 @@ public class AssetStatisticsController {
|
||||
private AssetStatisticsService assetStatisticsService;
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@GetMapping("/get-workplace")
|
||||
@Operation(summary = "查询工作台统计信息")
|
||||
public AssetWorkplaceStatisticsVO getWorkplaceStatisticsData() {
|
||||
return assetStatisticsService.getWorkplaceStatisticsData();
|
||||
@GetMapping("/host-type-chart")
|
||||
@Operation(summary = "查询主机类型图表")
|
||||
@PreAuthorize("@ss.hasPermission('asset:statistics:query')")
|
||||
public PieChartData getHostTypeChart() {
|
||||
return assetStatisticsService.getHostTypeChart();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ public class HostController {
|
||||
@PostMapping("/count")
|
||||
@Operation(summary = "查询主机数量")
|
||||
@PreAuthorize("@ss.hasPermission('asset:host:query')")
|
||||
public Long getHostExportCount(@Validated @RequestBody HostQueryRequest request) {
|
||||
public Long getHostCount(@Validated @RequestBody HostQueryRequest request) {
|
||||
return hostService.getHostCount(request);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
|
||||
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
|
||||
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
|
||||
import org.dromara.visor.module.asset.entity.request.host.HostExtraUpdateRequest;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.service.HostExtraService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.convert;
|
||||
|
||||
import org.dromara.visor.common.mapstruct.StringConversion;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostBaseDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
|
||||
import org.dromara.visor.module.asset.entity.vo.HostVO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* 主机 对外对象转换器
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023-9-11 14:16
|
||||
*/
|
||||
@Mapper(uses = StringConversion.class)
|
||||
public interface HostProviderConvert {
|
||||
|
||||
HostProviderConvert MAPPER = Mappers.getMapper(HostProviderConvert.class);
|
||||
|
||||
HostDO to(HostDTO host);
|
||||
|
||||
HostDTO to(HostDO domain);
|
||||
|
||||
HostDTO to(HostVO vo);
|
||||
|
||||
HostBaseDTO toBase(HostDO domain);
|
||||
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import org.apache.ibatis.annotations.Param;
|
||||
import org.dromara.visor.framework.mybatis.core.mapper.IMapper;
|
||||
import org.dromara.visor.framework.mybatis.core.query.Conditions;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostConfigDO;
|
||||
import org.dromara.visor.module.asset.entity.po.HostTypeCountPO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -133,4 +134,11 @@ public interface HostConfigDAO extends IMapper<HostConfigDO> {
|
||||
*/
|
||||
int setIdentityIdWithNull(@Param("identityIdList") List<Long> identityIdList);
|
||||
|
||||
/**
|
||||
* 查询启用的主机类型数量
|
||||
*
|
||||
* @return count
|
||||
*/
|
||||
List<HostTypeCountPO> selectEnabledTypeCount();
|
||||
|
||||
}
|
||||
|
||||
@@ -25,12 +25,12 @@ package org.dromara.visor.module.asset.define.cache;
|
||||
import cn.orionsec.kit.lang.define.cache.key.CacheKeyBuilder;
|
||||
import cn.orionsec.kit.lang.define.cache.key.CacheKeyDefine;
|
||||
import cn.orionsec.kit.lang.define.cache.key.struct.RedisCacheStruct;
|
||||
import org.dromara.visor.module.asset.entity.vo.AssetWorkplaceStatisticsVO;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 资产模块统计缓存 key
|
||||
* asset 模块统计缓存 key
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
@@ -38,12 +38,12 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
public interface AssetStatisticsCacheKeyDefine {
|
||||
|
||||
CacheKeyDefine WORKPLACE_DATA = new CacheKeyBuilder()
|
||||
.key("data:statistics:asset-workplace:{}:{}")
|
||||
.desc("资产模块工作台统计 ${userId} ${time}")
|
||||
.type(AssetWorkplaceStatisticsVO.class)
|
||||
CacheKeyDefine HOST_TYPE_COUNT = new CacheKeyBuilder()
|
||||
.key("data:statistics:host:count")
|
||||
.desc("主机类型数量")
|
||||
.type(JSONObject.class)
|
||||
.struct(RedisCacheStruct.STRING)
|
||||
.timeout(10, TimeUnit.MINUTES)
|
||||
.timeout(1, TimeUnit.DAYS)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class HostOperatorType extends InitializingOperatorTypes {
|
||||
new OperatorType(L, UPDATE, "修改主机 <sb>${name}</sb>"),
|
||||
new OperatorType(H, DELETE, "删除主机 <sb>${count}</sb> 条"),
|
||||
new OperatorType(M, UPDATE_STATUS, "修改主机状态 <sb>${name}</sb> - <sb>${status}</sb>"),
|
||||
new OperatorType(M, UPDATE_CONFIG, "修改主机配置 <sb>${name}</sb>"),
|
||||
new OperatorType(M, UPDATE_CONFIG, "修改主机配置 <sb>${name}</sb> - <sb>${type}</sb>"),
|
||||
new OperatorType(M, UPDATE_SPEC, "修改主机规格信息 <sb>${name}</sb>"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,10 +61,16 @@ public class HostIdentityCacheDTO implements LongCacheIdModel, Serializable {
|
||||
@Schema(description = "密钥id")
|
||||
private Long keyId;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间 资产页面展示")
|
||||
/**
|
||||
* 资产页面展示
|
||||
*/
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 资产页面展示
|
||||
*/
|
||||
@Schema(description = "修改时间")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.entity.po;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 主机类型数量对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/7/14 15:03
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "HostTypeCountPO", description = "主机类型数量对象")
|
||||
public class HostTypeCountPO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "数量")
|
||||
private Integer count;
|
||||
|
||||
}
|
||||
@@ -61,6 +61,7 @@ public class HostKeyCreateRequest implements Serializable {
|
||||
@Schema(description = "私钥文本")
|
||||
private String privateKey;
|
||||
|
||||
@NotBlank
|
||||
@ParamDecrypt
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
package org.dromara.visor.module.asset.enums;
|
||||
|
||||
/**
|
||||
* 主机认证类型 - ssh
|
||||
* 主机认证类型
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/9/21 19:01
|
||||
*/
|
||||
public enum HostSshAuthTypeEnum {
|
||||
public enum HostAuthTypeEnum {
|
||||
|
||||
/**
|
||||
* 密码认证
|
||||
@@ -48,11 +48,11 @@ public enum HostSshAuthTypeEnum {
|
||||
|
||||
;
|
||||
|
||||
public static HostSshAuthTypeEnum of(String type) {
|
||||
public static HostAuthTypeEnum of(String type) {
|
||||
if (type == null) {
|
||||
return PASSWORD;
|
||||
}
|
||||
for (HostSshAuthTypeEnum value : values()) {
|
||||
for (HostAuthTypeEnum value : values()) {
|
||||
if (value.name().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ package org.dromara.visor.module.asset.enums;
|
||||
* @version 1.0.0
|
||||
* @since 2023/12/20 21:41
|
||||
*/
|
||||
public enum HostExtraSshAuthTypeEnum {
|
||||
public enum HostExtraAuthTypeEnum {
|
||||
|
||||
/**
|
||||
* 默认认证方式
|
||||
@@ -48,11 +48,11 @@ public enum HostExtraSshAuthTypeEnum {
|
||||
|
||||
;
|
||||
|
||||
public static HostExtraSshAuthTypeEnum of(String type) {
|
||||
public static HostExtraAuthTypeEnum of(String type) {
|
||||
if (type == null) {
|
||||
return DEFAULT;
|
||||
}
|
||||
for (HostExtraSshAuthTypeEnum value : values()) {
|
||||
for (HostExtraAuthTypeEnum value : values()) {
|
||||
if (value.name().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.config;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Charsets;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
import org.dromara.visor.common.handler.data.strategy.AbstractGenericsDataStrategy;
|
||||
import org.dromara.visor.common.security.UpdatePasswordAction;
|
||||
import org.dromara.visor.common.utils.AesEncryptUtils;
|
||||
import org.dromara.visor.common.utils.RsaParamDecryptUtils;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.module.asset.enums.HostAuthTypeEnum;
|
||||
|
||||
/**
|
||||
* 主机配置策略基类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2025/3/31 19:44
|
||||
*/
|
||||
public abstract class AbstractHostConfigStrategy<T extends GenericsDataModel> extends AbstractGenericsDataStrategy<T> {
|
||||
|
||||
public AbstractHostConfigStrategy(Class<T> modelClass) {
|
||||
super(modelClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查加密密码
|
||||
*
|
||||
* @param before before
|
||||
* @param after after
|
||||
*/
|
||||
protected void checkEncryptPassword(String authType, UpdatePasswordAction before, UpdatePasswordAction after) {
|
||||
// 非密码认证/使用原始密码则直接赋值
|
||||
if (!HostAuthTypeEnum.PASSWORD.name().equals(authType) || !Booleans.isTrue(after.getUseNewPassword())) {
|
||||
if (before != null) {
|
||||
after.setPassword(before.getPassword());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 检查新密码
|
||||
String newPassword = Valid.notBlank(after.getPassword(), ErrorMessage.PASSWORD_MISSING);
|
||||
// 解密密码
|
||||
newPassword = RsaParamDecryptUtils.decrypt(newPassword);
|
||||
Valid.notBlank(newPassword, ErrorMessage.DECRYPT_ERROR);
|
||||
// 设置密码
|
||||
after.setPassword(AesEncryptUtils.encryptAsString(newPassword));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查编码格式
|
||||
*
|
||||
* @param charset charset
|
||||
*/
|
||||
protected void validCharset(String charset) {
|
||||
Valid.isTrue(Charsets.isSupported(charset), ErrorMessage.UNSUPPORTED_CHARSET, charset);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.visor.common.handler.data.GenericsStrategyDefinition;
|
||||
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
import org.dromara.visor.common.handler.data.strategy.GenericsDataStrategy;
|
||||
|
||||
/**
|
||||
* 主机配置类型策略枚举
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/9/11 14:37
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum HostConfigStrategyEnum implements GenericsStrategyDefinition {
|
||||
|
||||
/**
|
||||
* SSH
|
||||
*/
|
||||
SSH(HostSshConfigStrategy.class),
|
||||
|
||||
/**
|
||||
* RDP
|
||||
*/
|
||||
RDP(HostRdpConfigStrategy.class),
|
||||
|
||||
;
|
||||
|
||||
private final Class<? extends GenericsDataStrategy<? extends GenericsDataModel>> strategyClass;
|
||||
|
||||
public static HostConfigStrategyEnum of(String type) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
for (HostConfigStrategyEnum value : values()) {
|
||||
if (value.name().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.config;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostIdentityDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostRdpConfigDTO;
|
||||
import org.dromara.visor.module.asset.enums.HostAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostIdentityTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 主机 RDP 配置策略
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/9/19 14:26
|
||||
*/
|
||||
@Component
|
||||
public class HostRdpConfigStrategy extends AbstractHostConfigStrategy<HostRdpConfigDTO> {
|
||||
|
||||
@Resource
|
||||
private HostIdentityDAO hostIdentityDAO;
|
||||
|
||||
public HostRdpConfigStrategy() {
|
||||
super(HostRdpConfigDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostRdpConfigDTO getDefault() {
|
||||
return HostRdpConfigDTO.builder()
|
||||
.port(3389)
|
||||
.username(Const.ADMINISTRATOR)
|
||||
.authType(HostAuthTypeEnum.PASSWORD.name())
|
||||
.versionGt81(true)
|
||||
.timezone("Asia/Shanghai")
|
||||
.keyboardLayout("en-us-qwerty")
|
||||
.clipboardNormalize("preserve")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preValid(HostRdpConfigDTO model) {
|
||||
// 检查主机身份是否存在
|
||||
Long identityId = model.getIdentityId();
|
||||
if (identityId != null) {
|
||||
HostIdentityDO identity = Valid.notNull(hostIdentityDAO.selectById(identityId), ErrorMessage.IDENTITY_ABSENT);
|
||||
Valid.eq(HostIdentityTypeEnum.PASSWORD.name(), identity.getType(), ErrorMessage.CHECK_IDENTITY_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void valid(HostRdpConfigDTO model) {
|
||||
// 验证填充后的参数
|
||||
Valid.valid(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateFill(HostRdpConfigDTO beforeModel, HostRdpConfigDTO afterModel) {
|
||||
// 加密密码
|
||||
this.checkEncryptPassword(afterModel.getAuthType(), beforeModel, afterModel);
|
||||
afterModel.setHasPassword(null);
|
||||
afterModel.setUseNewPassword(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostRdpConfigDTO parse(String serialModel) {
|
||||
return HostTypeEnum.RDP.parse(serialModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toView(HostRdpConfigDTO model) {
|
||||
if (model == null) {
|
||||
return;
|
||||
}
|
||||
model.setHasPassword(Strings.isNotBlank(model.getPassword()));
|
||||
model.setPassword(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,21 +20,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.asset.handler.host.config.strategy;
|
||||
package org.dromara.visor.module.asset.handler.host.config;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Charsets;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.handler.data.strategy.AbstractGenericsDataStrategy;
|
||||
import org.dromara.visor.common.utils.AesEncryptUtils;
|
||||
import org.dromara.visor.common.utils.RsaParamDecryptUtils;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
|
||||
import org.dromara.visor.module.asset.dao.HostKeyDAO;
|
||||
import org.dromara.visor.module.asset.enums.HostSshAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostSshConfigDTO;
|
||||
import org.dromara.visor.module.asset.enums.HostAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -47,7 +43,7 @@ import javax.annotation.Resource;
|
||||
* @since 2023/9/19 14:26
|
||||
*/
|
||||
@Component
|
||||
public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshConfigModel> {
|
||||
public class HostSshConfigStrategy extends AbstractHostConfigStrategy<HostSshConfigDTO> {
|
||||
|
||||
@Resource
|
||||
private HostKeyDAO hostKeyDAO;
|
||||
@@ -55,18 +51,16 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
|
||||
@Resource
|
||||
private HostIdentityDAO hostIdentityDAO;
|
||||
|
||||
private static final String USERNAME = "root";
|
||||
|
||||
public HostSshConfigStrategy() {
|
||||
super(HostSshConfigModel.class);
|
||||
super(HostSshConfigDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostSshConfigModel getDefault() {
|
||||
return HostSshConfigModel.builder()
|
||||
public HostSshConfigDTO getDefault() {
|
||||
return HostSshConfigDTO.builder()
|
||||
.port(22)
|
||||
.username(USERNAME)
|
||||
.authType(HostSshAuthTypeEnum.PASSWORD.name())
|
||||
.username(Const.ROOT)
|
||||
.authType(HostAuthTypeEnum.PASSWORD.name())
|
||||
.connectTimeout(Const.MS_S_10)
|
||||
.charset(Const.UTF_8)
|
||||
.fileNameCharset(Const.UTF_8)
|
||||
@@ -75,7 +69,7 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preValid(HostSshConfigModel model) {
|
||||
protected void preValid(HostSshConfigDTO model) {
|
||||
// 验证编码格式
|
||||
this.validCharset(model.getCharset());
|
||||
this.validCharset(model.getFileNameCharset());
|
||||
@@ -93,21 +87,26 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void valid(HostSshConfigModel model) {
|
||||
protected void valid(HostSshConfigDTO model) {
|
||||
// 验证填充后的参数
|
||||
Valid.valid(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateFill(HostSshConfigModel beforeModel, HostSshConfigModel afterModel) {
|
||||
protected void updateFill(HostSshConfigDTO beforeModel, HostSshConfigDTO afterModel) {
|
||||
// 加密密码
|
||||
this.checkEncryptPassword(beforeModel, afterModel);
|
||||
this.checkEncryptPassword(afterModel.getAuthType(), beforeModel, afterModel);
|
||||
afterModel.setHasPassword(null);
|
||||
afterModel.setUseNewPassword(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toView(HostSshConfigModel model) {
|
||||
public HostSshConfigDTO parse(String serialModel) {
|
||||
return HostTypeEnum.SSH.parse(serialModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toView(HostSshConfigDTO model) {
|
||||
if (model == null) {
|
||||
return;
|
||||
}
|
||||
@@ -115,37 +114,4 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
|
||||
model.setPassword(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查加密密码
|
||||
*
|
||||
* @param before before
|
||||
* @param after after
|
||||
*/
|
||||
private void checkEncryptPassword(HostSshConfigModel before, HostSshConfigModel after) {
|
||||
// 非密码认证/使用原始密码则直接赋值
|
||||
if (!HostSshAuthTypeEnum.PASSWORD.name().equals(after.getAuthType())
|
||||
|| !Booleans.isTrue(after.getUseNewPassword())) {
|
||||
if (before != null) {
|
||||
after.setPassword(before.getPassword());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 检查新密码
|
||||
String newPassword = Valid.notBlank(after.getPassword(), ErrorMessage.PASSWORD_MISSING);
|
||||
// 解密密码
|
||||
newPassword = RsaParamDecryptUtils.decrypt(newPassword);
|
||||
Valid.notBlank(newPassword, ErrorMessage.DECRYPT_ERROR);
|
||||
// 设置密码
|
||||
after.setPassword(AesEncryptUtils.encryptAsString(newPassword));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查编码格式
|
||||
*
|
||||
* @param charset charset
|
||||
*/
|
||||
private void validCharset(String charset) {
|
||||
Valid.isTrue(Charsets.isSupported(charset), ErrorMessage.UNSUPPORTED_CHARSET, charset);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.asset.enums;
|
||||
package org.dromara.visor.module.asset.handler.host.extra;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -28,11 +28,12 @@ import org.dromara.visor.common.handler.data.GenericsStrategyDefinition;
|
||||
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
import org.dromara.visor.common.handler.data.strategy.GenericsDataStrategy;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostLabelExtraStrategy;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostRdpExtraStrategy;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostSpecExtraStrategy;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostSshExtraStrategy;
|
||||
|
||||
/**
|
||||
* 主机额外配置项枚举
|
||||
* 主机额外配置项策略枚举
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
@@ -42,15 +43,20 @@ import org.dromara.visor.module.asset.handler.host.extra.strategy.HostSshExtraSt
|
||||
@AllArgsConstructor
|
||||
public enum HostExtraItemEnum implements GenericsStrategyDefinition {
|
||||
|
||||
/**
|
||||
* 标签额外配置
|
||||
*/
|
||||
LABEL(HostLabelExtraStrategy.class, true),
|
||||
|
||||
/**
|
||||
* SSH 额外配置
|
||||
*/
|
||||
SSH(HostSshExtraStrategy.class, true),
|
||||
|
||||
/**
|
||||
* 标签额外配置
|
||||
* RDP 额外配置
|
||||
*/
|
||||
LABEL(HostLabelExtraStrategy.class, true),
|
||||
RDP(HostRdpExtraStrategy.class, true),
|
||||
|
||||
/**
|
||||
* 规格信息配置
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.extra.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
|
||||
/**
|
||||
* 主机拓展信息 - rdp 模型
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/12/20 21:36
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HostRdpExtraModel implements GenericsDataModel {
|
||||
|
||||
/**
|
||||
* 认证方式
|
||||
*/
|
||||
private String authType;
|
||||
|
||||
/**
|
||||
* 主机身份
|
||||
*/
|
||||
private Long identityId;
|
||||
|
||||
/**
|
||||
* 低带宽模式
|
||||
*/
|
||||
private Boolean lowBandwidthMode;
|
||||
|
||||
}
|
||||
@@ -53,12 +53,14 @@ public class HostLabelExtraStrategy extends AbstractGenericsDataStrategy<HostLab
|
||||
|
||||
@Override
|
||||
public void updateFill(HostLabelExtraModel beforeModel, HostLabelExtraModel afterModel) {
|
||||
// 为空则覆盖
|
||||
if (afterModel.getAlias() == null) {
|
||||
afterModel.setAlias(beforeModel.getAlias());
|
||||
}
|
||||
if (afterModel.getColor() == null) {
|
||||
afterModel.setColor(beforeModel.getColor());
|
||||
if (beforeModel != null) {
|
||||
// 为空则覆盖
|
||||
if (afterModel.getAlias() == null) {
|
||||
afterModel.setAlias(beforeModel.getAlias());
|
||||
}
|
||||
if (afterModel.getColor() == null) {
|
||||
afterModel.setColor(beforeModel.getColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.extra.strategy;
|
||||
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.handler.data.strategy.AbstractGenericsDataStrategy;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
|
||||
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostRdpExtraModel;
|
||||
import org.dromara.visor.module.infra.api.DataPermissionApi;
|
||||
import org.dromara.visor.module.infra.enums.DataPermissionTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 主机拓展信息 - rdp 模型处理策略
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/12/20 22:17
|
||||
*/
|
||||
@Component
|
||||
public class HostRdpExtraStrategy extends AbstractGenericsDataStrategy<HostRdpExtraModel> {
|
||||
|
||||
@Resource
|
||||
private HostIdentityDAO hostIdentityDAO;
|
||||
|
||||
@Resource
|
||||
private DataPermissionApi dataPermissionApi;
|
||||
|
||||
public HostRdpExtraStrategy() {
|
||||
super(HostRdpExtraModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostRdpExtraModel getDefault() {
|
||||
return HostRdpExtraModel.builder()
|
||||
.authType(HostExtraAuthTypeEnum.DEFAULT.name())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preValid(HostRdpExtraModel model) {
|
||||
HostExtraAuthTypeEnum authType = Valid.valid(HostExtraAuthTypeEnum::of, model.getAuthType());
|
||||
model.setAuthType(authType.name());
|
||||
Long identityId = model.getIdentityId();
|
||||
Long userId = SecurityUtils.getLoginUserId();
|
||||
// 必填验证
|
||||
if (HostExtraAuthTypeEnum.CUSTOM_IDENTITY.equals(authType)) {
|
||||
Valid.notNull(identityId);
|
||||
// 验证主机身份是否存在
|
||||
Valid.notNull(hostIdentityDAO.selectById(identityId), ErrorMessage.IDENTITY_ABSENT);
|
||||
// 验证主机身份是否有权限
|
||||
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_IDENTITY, userId, identityId),
|
||||
ErrorMessage.ANY_NO_PERMISSION,
|
||||
DataPermissionTypeEnum.HOST_IDENTITY.getPermissionName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
|
||||
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
|
||||
import org.dromara.visor.module.asset.dao.HostKeyDAO;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraSshAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostSshExtraModel;
|
||||
import org.dromara.visor.module.infra.api.DataPermissionApi;
|
||||
import org.dromara.visor.module.infra.enums.DataPermissionTypeEnum;
|
||||
@@ -62,20 +62,20 @@ public class HostSshExtraStrategy extends AbstractGenericsDataStrategy<HostSshEx
|
||||
@Override
|
||||
public HostSshExtraModel getDefault() {
|
||||
return HostSshExtraModel.builder()
|
||||
.authType(HostExtraSshAuthTypeEnum.DEFAULT.name())
|
||||
.authType(HostExtraAuthTypeEnum.DEFAULT.name())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preValid(HostSshExtraModel model) {
|
||||
HostExtraSshAuthTypeEnum authType = Valid.valid(HostExtraSshAuthTypeEnum::of, model.getAuthType());
|
||||
HostExtraAuthTypeEnum authType = Valid.valid(HostExtraAuthTypeEnum::of, model.getAuthType());
|
||||
model.setAuthType(authType.name());
|
||||
Long keyId = model.getKeyId();
|
||||
Long identityId = model.getIdentityId();
|
||||
// 必填验证
|
||||
if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(authType)) {
|
||||
if (HostExtraAuthTypeEnum.CUSTOM_KEY.equals(authType)) {
|
||||
Valid.notNull(keyId);
|
||||
} else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(authType)) {
|
||||
} else if (HostExtraAuthTypeEnum.CUSTOM_IDENTITY.equals(authType)) {
|
||||
Valid.notNull(identityId);
|
||||
}
|
||||
// 验证主机密钥是否存在
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.terminal;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.module.asset.define.AssetThreadPools;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.enums.InputTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.manager.TerminalManager;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 终端处理器
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/12/28 14:33
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TerminalMessageDispatcher extends AbstractWebSocketHandler {
|
||||
|
||||
@Resource
|
||||
private TerminalManager terminalManager;
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||
String payload = message.getPayload();
|
||||
try {
|
||||
// 解析类型
|
||||
InputTypeEnum type = InputTypeEnum.of(payload);
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
// 解析并处理消息
|
||||
if (type.isAsyncExec()) {
|
||||
// 异步执行
|
||||
AssetThreadPools.TERMINAL_OPERATOR.execute(() -> {
|
||||
type.getHandler().handle(session, type.parse(payload));
|
||||
});
|
||||
} else {
|
||||
// 同步执行
|
||||
type.getHandler().handle(session, type.parse(payload));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("TerminalDispatchHandler-handleMessage-error id: {}, msg: {}", session.getId(), payload, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
log.info("TerminalMessageDispatcher-afterConnectionEstablished id: {}", session.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportError(WebSocketSession session, Throwable exception) {
|
||||
log.error("TerminalMessageDispatcher-handleTransportError id: {}", session.getId(), exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String id = session.getId();
|
||||
log.info("TerminalMessageDispatcher-afterConnectionClosed id: {}, code: {}, reason: {}", id, status.getCode(), status.getReason());
|
||||
// 关闭会话
|
||||
terminalManager.closeSession(id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.terminal.handler;
|
||||
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorLogModel;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.enums.OutputTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.manager.TerminalManager;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.TerminalBasePayload;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.TerminalConfig;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.session.ITerminalSession;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.utils.TerminalUtils;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 终端消息处理器 基类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/12/29 18:59
|
||||
*/
|
||||
public abstract class AbstractTerminalHandler<T extends TerminalBasePayload> implements ITerminalHandler<T> {
|
||||
|
||||
@Resource
|
||||
protected TerminalManager terminalManager;
|
||||
|
||||
@Resource
|
||||
private OperatorLogFrameworkService operatorLogFrameworkService;
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param channel channel
|
||||
* @param type type
|
||||
* @param body body
|
||||
* @param <E> E
|
||||
*/
|
||||
public <E extends TerminalBasePayload> void send(WebSocketSession channel, OutputTypeEnum type, E body) {
|
||||
body.setType(type.getType());
|
||||
// 发送消息
|
||||
this.send(channel, type.format(body));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param channel channel
|
||||
* @param message message
|
||||
*/
|
||||
protected void send(WebSocketSession channel, String message) {
|
||||
WebSockets.sendText(channel, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存操作日志
|
||||
*
|
||||
* @param payload payload
|
||||
* @param channel channel
|
||||
* @param extra extra
|
||||
* @param type type
|
||||
* @param startTime startTime
|
||||
* @param ex ex
|
||||
*/
|
||||
protected void saveOperatorLog(T payload,
|
||||
WebSocketSession channel,
|
||||
Map<String, Object> extra,
|
||||
String type,
|
||||
long startTime,
|
||||
Exception ex) {
|
||||
String channelId = channel.getId();
|
||||
String sessionId = payload.getSessionId();
|
||||
// 获取会话并且设置参数
|
||||
ITerminalSession session = terminalManager.getSession(channelId, sessionId);
|
||||
if (session != null) {
|
||||
TerminalConfig config = session.getConfig();
|
||||
extra.put(OperatorLogs.HOST_ID, config.getHostId());
|
||||
extra.put(OperatorLogs.HOST_NAME, config.getHostName());
|
||||
extra.put(OperatorLogs.ADDRESS, config.getAddress());
|
||||
}
|
||||
extra.put(OperatorLogs.CHANNEL_ID, channelId);
|
||||
extra.put(OperatorLogs.SESSION_ID, sessionId);
|
||||
// 获取日志
|
||||
OperatorLogModel model = TerminalUtils.getOperatorLogModel(channel, extra, type, startTime, ex);
|
||||
// 保存
|
||||
operatorLogFrameworkService.insert(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
*
|
||||
* @param ex ex
|
||||
* @return msg
|
||||
*/
|
||||
protected String getErrorMessage(Exception ex) {
|
||||
// 获取错误信息
|
||||
return ErrorMessage.getErrorMessage(ex, ErrorMessage.OPERATE_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.terminal.handler;
|
||||
|
||||
import cn.orionsec.kit.lang.exception.DisabledException;
|
||||
import cn.orionsec.kit.lang.exception.argument.InvalidArgumentException;
|
||||
import cn.orionsec.kit.lang.utils.Exceptions;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.ExtraFieldConst;
|
||||
import org.dromara.visor.common.enums.BooleanBit;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorLogModel;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.asset.dao.HostDAO;
|
||||
import org.dromara.visor.module.asset.define.operator.TerminalOperatorType;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.TerminalConnectLogDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.entity.request.host.TerminalConnectLogCreateRequest;
|
||||
import org.dromara.visor.module.asset.enums.TerminalConnectStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.TerminalConnectTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.constant.TerminalMessage;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.enums.OutputTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.request.TerminalCheckRequest;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.response.TerminalCheckResponse;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.session.ITerminalSession;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.utils.TerminalUtils;
|
||||
import org.dromara.visor.module.asset.service.HostConnectService;
|
||||
import org.dromara.visor.module.asset.service.TerminalConnectLogService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 终端连接检查
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/12/29 15:32
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckRequest> {
|
||||
|
||||
@Resource
|
||||
private HostDAO hostDAO;
|
||||
|
||||
@Resource
|
||||
private HostConnectService hostConnectService;
|
||||
|
||||
@Resource
|
||||
private TerminalConnectLogService terminalConnectLogService;
|
||||
|
||||
@Resource
|
||||
private OperatorLogFrameworkService operatorLogFrameworkService;
|
||||
|
||||
@Override
|
||||
public void handle(WebSocketSession channel, TerminalCheckRequest payload) {
|
||||
Long hostId = payload.getHostId();
|
||||
Long userId = WebSockets.getAttr(channel, ExtraFieldConst.USER_ID);
|
||||
long startTime = System.currentTimeMillis();
|
||||
TerminalConnectTypeEnum connectType = TerminalConnectTypeEnum.of(payload.getConnectType());
|
||||
String sessionId = payload.getSessionId();
|
||||
log.info("TerminalCheckHandler-handle start userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId);
|
||||
// 检查 session 是否存在
|
||||
if (this.checkSession(channel, payload)) {
|
||||
log.info("TerminalCheckHandler-handle present session userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId);
|
||||
return;
|
||||
}
|
||||
// 获取主机信息
|
||||
HostDO host = this.checkHost(channel, payload, hostId);
|
||||
if (host == null) {
|
||||
log.info("TerminalCheckHandler-handle unknown host userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId);
|
||||
return;
|
||||
}
|
||||
TerminalConnectDTO connect = null;
|
||||
Exception ex = null;
|
||||
try {
|
||||
// 获取连接信息
|
||||
connect = hostConnectService.getSshConnectInfo(host, userId);
|
||||
connect.setConnectType(connectType.name());
|
||||
// 设置到缓存中
|
||||
channel.getAttributes().put(sessionId, connect);
|
||||
log.info("TerminalCheckHandler-handle success userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId);
|
||||
} catch (InvalidArgumentException e) {
|
||||
ex = e;
|
||||
log.error("TerminalCheckHandler-handle start error userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId, e);
|
||||
} catch (DisabledException e) {
|
||||
ex = Exceptions.runtime(TerminalMessage.CONFIG_DISABLED);
|
||||
log.error("TerminalCheckHandler-handle disabled error userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId);
|
||||
} catch (Exception e) {
|
||||
ex = Exceptions.runtime(TerminalMessage.CONNECTION_FAILED);
|
||||
log.error("TerminalCheckHandler-handle exception userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId, e);
|
||||
}
|
||||
// 记录主机日志
|
||||
TerminalConnectLogDO connectLog = this.saveHostLog(channel, userId, host, startTime, ex, sessionId, connectType);
|
||||
if (connect != null) {
|
||||
connect.setLogId(connectLog.getId());
|
||||
}
|
||||
// 响应检查结果
|
||||
this.send(channel,
|
||||
OutputTypeEnum.CHECK,
|
||||
TerminalCheckResponse.builder()
|
||||
.sessionId(payload.getSessionId())
|
||||
.result(BooleanBit.of(ex == null).getValue())
|
||||
.msg(ex == null ? null : ex.getMessage())
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查会话是否存在
|
||||
*
|
||||
* @param channel channel
|
||||
* @param payload payload
|
||||
* @return 是否存在
|
||||
*/
|
||||
private boolean checkSession(WebSocketSession channel, TerminalCheckRequest payload) {
|
||||
ITerminalSession session = terminalManager.getSession(channel.getId(), payload.getSessionId());
|
||||
if (session != null) {
|
||||
this.sendCheckFailedMessage(channel, payload, ErrorMessage.SESSION_PRESENT);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主机信息
|
||||
*
|
||||
* @param channel channel
|
||||
* @param payload payload
|
||||
* @param hostId hostId
|
||||
* @return host
|
||||
*/
|
||||
private HostDO checkHost(WebSocketSession channel, TerminalCheckRequest payload, Long hostId) {
|
||||
// 查询主机信息
|
||||
HostDO host = hostDAO.selectById(hostId);
|
||||
// 不存在返回错误信息
|
||||
if (host == null) {
|
||||
this.sendCheckFailedMessage(channel, payload, ErrorMessage.HOST_ABSENT);
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送检查失败消息
|
||||
*
|
||||
* @param channel channel
|
||||
* @param payload payload
|
||||
* @param msg msg
|
||||
*/
|
||||
private void sendCheckFailedMessage(WebSocketSession channel, TerminalCheckRequest payload, String msg) {
|
||||
TerminalCheckResponse resp = TerminalCheckResponse.builder()
|
||||
.sessionId(payload.getSessionId())
|
||||
.result(BooleanBit.FALSE.getValue())
|
||||
.msg(msg)
|
||||
.build();
|
||||
// 发送
|
||||
this.send(channel, OutputTypeEnum.CHECK, resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录主机日志
|
||||
*
|
||||
* @param channel channel
|
||||
* @param userId userId
|
||||
* @param host host
|
||||
* @param startTime startTime
|
||||
* @param ex ex
|
||||
* @param sessionId sessionId
|
||||
* @param connectType connectType
|
||||
* @return connectLog
|
||||
*/
|
||||
private TerminalConnectLogDO saveHostLog(WebSocketSession channel,
|
||||
Long userId,
|
||||
HostDO host,
|
||||
long startTime,
|
||||
Exception ex,
|
||||
String sessionId,
|
||||
TerminalConnectTypeEnum connectType) {
|
||||
Long hostId = host.getId();
|
||||
String hostName = host.getName();
|
||||
String username = WebSockets.getAttr(channel, ExtraFieldConst.USERNAME);
|
||||
// 额外参数
|
||||
Map<String, Object> extra = Maps.newMap();
|
||||
extra.put(OperatorLogs.HOST_ID, hostId);
|
||||
extra.put(OperatorLogs.HOST_NAME, hostName);
|
||||
extra.put(OperatorLogs.CONNECT_TYPE, connectType.name());
|
||||
extra.put(OperatorLogs.CHANNEL_ID, channel.getId());
|
||||
extra.put(OperatorLogs.SESSION_ID, sessionId);
|
||||
// 日志参数
|
||||
OperatorLogModel logModel = TerminalUtils.getOperatorLogModel(channel, extra,
|
||||
TerminalOperatorType.CONNECT, startTime, ex);
|
||||
// 记录操作日志
|
||||
operatorLogFrameworkService.insert(logModel);
|
||||
// 记录连接日志
|
||||
TerminalConnectLogCreateRequest connectLog = TerminalConnectLogCreateRequest.builder()
|
||||
.userId(userId)
|
||||
.username(username)
|
||||
.hostId(hostId)
|
||||
.hostName(hostName)
|
||||
.hostAddress(host.getAddress())
|
||||
.status(ex == null ? TerminalConnectStatusEnum.CONNECTING.name() : TerminalConnectStatusEnum.FAILED.name())
|
||||
.sessionId(sessionId)
|
||||
.extra(extra)
|
||||
.build();
|
||||
// 填充其他信息
|
||||
extra.put(OperatorLogs.TRACE_ID, logModel.getTraceId());
|
||||
extra.put(OperatorLogs.ADDRESS, logModel.getAddress());
|
||||
extra.put(OperatorLogs.LOCATION, logModel.getLocation());
|
||||
extra.put(OperatorLogs.USER_AGENT, logModel.getUserAgent());
|
||||
extra.put(OperatorLogs.ERROR_MESSAGE, logModel.getErrorMessage());
|
||||
// 记录连接日志
|
||||
return terminalConnectLogService.create(connectType, connectLog);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.terminal.handler;
|
||||
|
||||
import cn.orionsec.kit.lang.exception.AuthenticationException;
|
||||
import cn.orionsec.kit.lang.exception.TimeoutException;
|
||||
import cn.orionsec.kit.lang.exception.argument.InvalidArgumentException;
|
||||
import cn.orionsec.kit.lang.utils.Exceptions;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.net.host.SessionStore;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.ExtraFieldConst;
|
||||
import org.dromara.visor.common.enums.BooleanBit;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.asset.define.config.AppSftpConfig;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.enums.TerminalConnectStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.TerminalConnectTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.jsch.SessionStores;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.constant.TerminalMessage;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.enums.OutputTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.TerminalConfig;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.request.TerminalConnectRequest;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.response.TerminalConnectResponse;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.session.ITerminalSession;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.session.SftpSession;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.session.SshSession;
|
||||
import org.dromara.visor.module.asset.service.TerminalConnectLogService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 连接主机处理器
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/12/29 15:32
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConnectRequest> {
|
||||
|
||||
@Resource
|
||||
private AppSftpConfig appSftpConfig;
|
||||
|
||||
@Resource
|
||||
private TerminalConnectLogService terminalConnectLogService;
|
||||
|
||||
@Override
|
||||
public void handle(WebSocketSession channel, TerminalConnectRequest payload) {
|
||||
String sessionId = payload.getSessionId();
|
||||
log.info("TerminalConnectHandler-handle start sessionId: {}", sessionId);
|
||||
// 获取终端连接信息
|
||||
TerminalConnectDTO connect = WebSockets.getAttr(channel, sessionId);
|
||||
if (connect == null) {
|
||||
log.info("TerminalConnectHandler-handle unknown sessionId: {}", sessionId);
|
||||
this.send(channel,
|
||||
OutputTypeEnum.CONNECT,
|
||||
TerminalConnectResponse.builder()
|
||||
.sessionId(payload.getSessionId())
|
||||
.result(BooleanBit.FALSE.getValue())
|
||||
.msg(ErrorMessage.SESSION_ABSENT)
|
||||
.build());
|
||||
return;
|
||||
}
|
||||
// 移除会话连接信息
|
||||
channel.getAttributes().remove(sessionId);
|
||||
Exception ex = null;
|
||||
ITerminalSession session = null;
|
||||
try {
|
||||
// 连接主机
|
||||
session = this.connect(sessionId, connect, channel, payload);
|
||||
// 添加会话到 manager
|
||||
terminalManager.addSession(session);
|
||||
} catch (Exception e) {
|
||||
ex = e;
|
||||
Streams.close(session);
|
||||
// 修改连接状态为失败
|
||||
Map<String, Object> extra = Maps.newMap(4);
|
||||
extra.put(ExtraFieldConst.ERROR_MESSAGE, this.getConnectErrorMessage(e));
|
||||
terminalConnectLogService.updateStatusById(connect.getLogId(), TerminalConnectStatusEnum.FAILED, extra);
|
||||
}
|
||||
// 返回连接状态
|
||||
this.send(channel,
|
||||
OutputTypeEnum.CONNECT,
|
||||
TerminalConnectResponse.builder()
|
||||
.sessionId(payload.getSessionId())
|
||||
.result(BooleanBit.of(ex == null).getValue())
|
||||
.msg(this.getConnectErrorMessage(ex))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接主机
|
||||
*
|
||||
* @param sessionId sessionId
|
||||
* @param connect connect
|
||||
* @param channel channel
|
||||
* @param body body
|
||||
* @return channel
|
||||
*/
|
||||
private ITerminalSession connect(String sessionId,
|
||||
TerminalConnectDTO connect,
|
||||
WebSocketSession channel,
|
||||
TerminalConnectRequest body) {
|
||||
String connectType = connect.getConnectType();
|
||||
ITerminalSession session = null;
|
||||
try {
|
||||
// 连接配置
|
||||
TerminalConfig config = TerminalConfig.builder()
|
||||
.logId(connect.getLogId())
|
||||
.hostId(connect.getHostId())
|
||||
.hostName(connect.getHostName())
|
||||
.address(connect.getHostAddress())
|
||||
.charset(connect.getCharset())
|
||||
.fileNameCharset(connect.getFileNameCharset())
|
||||
.fileContentCharset(connect.getFileContentCharset())
|
||||
.filePreviewSize(appSftpConfig.getPreviewSize())
|
||||
.build();
|
||||
// 建立连接
|
||||
SessionStore sessionStore = SessionStores.openSessionStore(connect);
|
||||
if (TerminalConnectTypeEnum.SSH.name().equals(connectType)) {
|
||||
// 打开 ssh 会话
|
||||
SshSession sshSession = new SshSession(sessionId, channel, sessionStore, config);
|
||||
sshSession.connect(body.getTerminalType(), body.getCols(), body.getRows());
|
||||
session = sshSession;
|
||||
} else if (TerminalConnectTypeEnum.SFTP.name().equals(connectType)) {
|
||||
// 打开 sftp 会话
|
||||
SftpSession sftpSession = new SftpSession(sessionId, channel, sessionStore, config);
|
||||
sftpSession.connect();
|
||||
session = sftpSession;
|
||||
}
|
||||
log.info("TerminalConnectHandler-handle success sessionId: {}", sessionId);
|
||||
return session;
|
||||
} catch (Exception e) {
|
||||
Streams.close(session);
|
||||
log.error("TerminalConnectHandler-handle error sessionId: {}", sessionId, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取建立连接错误信息
|
||||
*
|
||||
* @param e e
|
||||
* @return errorMessage
|
||||
*/
|
||||
private String getConnectErrorMessage(Exception e) {
|
||||
if (e == null) {
|
||||
return null;
|
||||
}
|
||||
if (Exceptions.isCausedBy(e, TimeoutException.class)) {
|
||||
// 连接超时
|
||||
return TerminalMessage.CONNECTION_TIMEOUT;
|
||||
} else if (Exceptions.isCausedBy(e, AuthenticationException.class)) {
|
||||
// 认证失败
|
||||
return TerminalMessage.AUTHENTICATION_FAILURE;
|
||||
} else if (Exceptions.isCausedBy(e, InvalidArgumentException.class)) {
|
||||
// 参数错误
|
||||
return e.getMessage();
|
||||
} else {
|
||||
// 其他错误
|
||||
return TerminalMessage.SERVER_UNREACHABLE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.terminal.manager;
|
||||
|
||||
import cn.orionsec.kit.lang.define.collect.MultiConcurrentHashMap;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.session.ITerminalSession;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 终端管理器
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/1/3 11:35
|
||||
*/
|
||||
@Component
|
||||
public class TerminalManager {
|
||||
|
||||
/**
|
||||
* 会话存储器
|
||||
*/
|
||||
private final MultiConcurrentHashMap<String, String, ITerminalSession> channelSessions = MultiConcurrentHashMap.create();
|
||||
|
||||
/**
|
||||
* 添加会话
|
||||
*
|
||||
* @param session session
|
||||
*/
|
||||
public void addSession(ITerminalSession session) {
|
||||
channelSessions.put(session.getChannelId(), session.getSessionId(), session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 channel 关闭会话
|
||||
*
|
||||
* @param channelId channelId
|
||||
*/
|
||||
public void closeSession(String channelId) {
|
||||
// 获取并移除
|
||||
ConcurrentHashMap<String, ITerminalSession> session = channelSessions.remove(channelId);
|
||||
if (!Maps.isEmpty(session)) {
|
||||
session.values().forEach(Streams::close);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 channel + sessionId 关闭会话
|
||||
*
|
||||
* @param channelId channelId
|
||||
* @param sessionId sessionId
|
||||
*/
|
||||
public void closeSession(String channelId, String sessionId) {
|
||||
// 获取并移除
|
||||
ITerminalSession session = channelSessions.removeElement(channelId, sessionId);
|
||||
if (session != null) {
|
||||
Streams.close(session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话
|
||||
*
|
||||
* @param channelId channelId
|
||||
* @param sessionId sessionId
|
||||
* @param <T> T
|
||||
* @return session
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ITerminalSession> T getSession(String channelId, String sessionId) {
|
||||
return (T) channelSessions.get(channelId, sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话
|
||||
*
|
||||
* @param channelId channelId
|
||||
* @return session
|
||||
*/
|
||||
public Map<String, ITerminalSession> getSession(String channelId) {
|
||||
return channelSessions.get(channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部会话
|
||||
*
|
||||
* @return session
|
||||
*/
|
||||
public MultiConcurrentHashMap<String, String, ITerminalSession> getChannelSessions() {
|
||||
return channelSessions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed 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.dromara.visor.module.asset.handler.host.terminal.session;
|
||||
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.enums.BooleanBit;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.asset.enums.TerminalConnectStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.constant.TerminalMessage;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.enums.OutputTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.TerminalConfig;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.model.response.TerminalCloseResponse;
|
||||
import org.dromara.visor.module.asset.service.TerminalConnectLogService;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
/**
|
||||
* 终端会话基类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/2/4 16:51
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class TerminalSession implements ITerminalSession {
|
||||
|
||||
@Getter
|
||||
protected final String sessionId;
|
||||
|
||||
protected final WebSocketSession channel;
|
||||
|
||||
@Getter
|
||||
protected final TerminalConfig config;
|
||||
|
||||
@Getter
|
||||
protected volatile boolean closed;
|
||||
|
||||
protected volatile boolean forceOffline;
|
||||
|
||||
public TerminalSession(String sessionId, WebSocketSession channel, TerminalConfig config) {
|
||||
this.sessionId = sessionId;
|
||||
this.channel = channel;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
protected abstract void releaseResource();
|
||||
|
||||
/**
|
||||
* 发送关闭消息
|
||||
*/
|
||||
protected void sendCloseMessage() {
|
||||
log.info("TerminalSession close {}, forClose: {}, forceOffline: {}", sessionId, this.closed, this.forceOffline);
|
||||
// 发送关闭信息
|
||||
TerminalCloseResponse resp = TerminalCloseResponse.builder()
|
||||
.type(OutputTypeEnum.CLOSE.getType())
|
||||
.sessionId(this.sessionId)
|
||||
.forceClose(BooleanBit.of(this.forceOffline).getValue())
|
||||
.msg(this.forceOffline ? TerminalMessage.FORCED_OFFLINE : TerminalMessage.CONNECTION_CLOSED)
|
||||
.build();
|
||||
WebSockets.sendText(channel, OutputTypeEnum.CLOSE.format(resp));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
log.info("terminal close {}", sessionId);
|
||||
// 检查并且关闭
|
||||
if (this.checkAndClose()) {
|
||||
// 修改状态
|
||||
SpringHolder.getBean(TerminalConnectLogService.class)
|
||||
.updateStatusById(config.getLogId(), TerminalConnectStatusEnum.COMPLETE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceOffline() {
|
||||
log.info("terminal forceOffline {}", sessionId);
|
||||
this.forceOffline = true;
|
||||
// 关闭
|
||||
this.checkAndClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并且关闭会话
|
||||
*
|
||||
* @return close
|
||||
*/
|
||||
private boolean checkAndClose() {
|
||||
if (closed) {
|
||||
return false;
|
||||
}
|
||||
this.closed = true;
|
||||
// 释放资源
|
||||
try {
|
||||
this.releaseResource();
|
||||
} catch (Exception e) {
|
||||
log.error("terminal release error {}", sessionId, e);
|
||||
}
|
||||
// 发送关闭信息
|
||||
try {
|
||||
this.sendCloseMessage();
|
||||
} catch (Exception e) {
|
||||
log.error("terminal send close error {}", sessionId, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelId() {
|
||||
return channel.getId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,41 +22,22 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.service;
|
||||
|
||||
import org.dromara.visor.common.entity.chart.LineSingleChartData;
|
||||
import org.dromara.visor.module.asset.entity.vo.AssetWorkplaceStatisticsVO;
|
||||
import org.dromara.visor.module.asset.enums.ExecSourceEnum;
|
||||
import org.dromara.visor.common.entity.chart.PieChartData;
|
||||
|
||||
/**
|
||||
* 资产模块统计服务
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/12/23 22:24
|
||||
* @since 2025/3/7 16:26
|
||||
*/
|
||||
public interface AssetStatisticsService {
|
||||
|
||||
/**
|
||||
* 查询工作台统计信息
|
||||
* 获取主机类型图表
|
||||
*
|
||||
* @return data
|
||||
*/
|
||||
AssetWorkplaceStatisticsVO getWorkplaceStatisticsData();
|
||||
|
||||
/**
|
||||
* 获取用户终端连接日志数量图表
|
||||
*
|
||||
* @param userId userId
|
||||
* @return data
|
||||
*/
|
||||
LineSingleChartData getTerminalConnectCountChart(Long userId);
|
||||
|
||||
/**
|
||||
* 获取用户执行日志数量图表
|
||||
*
|
||||
* @param userId userId
|
||||
* @param source source
|
||||
* @return chart
|
||||
*/
|
||||
LineSingleChartData getUserExecLogCountChart(Long userId, ExecSourceEnum source);
|
||||
PieChartData getHostTypeChart();
|
||||
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.service;
|
||||
|
||||
import org.dromara.visor.common.session.config.RdpConnectConfig;
|
||||
import org.dromara.visor.common.session.config.SshConnectConfig;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.entity.request.host.HostTestConnectRequest;
|
||||
|
||||
/**
|
||||
@@ -43,29 +44,55 @@ public interface HostConnectService {
|
||||
void testHostConnect(HostTestConnectRequest request);
|
||||
|
||||
/**
|
||||
* 获取 SSH 连接信息
|
||||
* 获取 SSH 连接配置
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @return session
|
||||
*/
|
||||
TerminalConnectDTO getSshConnectInfo(Long hostId);
|
||||
SshConnectConfig getSshConnectConfig(Long hostId);
|
||||
|
||||
/**
|
||||
* 使用用户配置获取 SSH 连接信息
|
||||
* 使用用户配置获取 SSH 连接配置
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @param userId userId
|
||||
* @return session
|
||||
*/
|
||||
TerminalConnectDTO getSshConnectInfo(Long hostId, Long userId);
|
||||
SshConnectConfig getSshConnectConfig(Long hostId, Long userId);
|
||||
|
||||
/**
|
||||
* 使用用户配置获取 SSH 连接信息
|
||||
* 使用用户配置获取 SSH 连接配置
|
||||
*
|
||||
* @param host host
|
||||
* @param userId userId
|
||||
* @return session
|
||||
*/
|
||||
TerminalConnectDTO getSshConnectInfo(HostDO host, Long userId);
|
||||
SshConnectConfig getSshConnectConfig(HostDO host, Long userId);
|
||||
|
||||
/**
|
||||
* 获取 RDP 连接配置
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @return session
|
||||
*/
|
||||
RdpConnectConfig getRdpConnectConfig(Long hostId);
|
||||
|
||||
/**
|
||||
* 使用用户配置获取 RDP 连接配置
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @param userId userId
|
||||
* @return session
|
||||
*/
|
||||
RdpConnectConfig getRdpConnectConfig(Long hostId, Long userId);
|
||||
|
||||
/**
|
||||
* 使用用户配置获取 RDP 连接配置
|
||||
*
|
||||
* @param host host
|
||||
* @param userId userId
|
||||
* @return session
|
||||
*/
|
||||
RdpConnectConfig getRdpConnectConfig(HostDO host, Long userId);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ package org.dromara.visor.module.asset.service;
|
||||
|
||||
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
import org.dromara.visor.module.asset.entity.request.host.HostExtraUpdateRequest;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostSpecExtraModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -34,8 +34,8 @@ import org.dromara.visor.module.asset.convert.HostGroupConvert;
|
||||
import org.dromara.visor.module.asset.dao.HostDAO;
|
||||
import org.dromara.visor.module.asset.entity.request.asset.AssetAuthorizedDataQueryRequest;
|
||||
import org.dromara.visor.module.asset.entity.vo.*;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostLabelExtraModel;
|
||||
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
|
||||
import org.dromara.visor.module.asset.service.HostIdentityService;
|
||||
|
||||
@@ -22,157 +22,61 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.service.impl;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import org.dromara.visor.common.entity.StatisticsRange;
|
||||
import org.dromara.visor.common.entity.chart.LineSingleChartData;
|
||||
import cn.orionsec.kit.lang.utils.Objects1;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.dromara.visor.common.entity.chart.PieChartData;
|
||||
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
|
||||
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
|
||||
import org.dromara.visor.module.asset.convert.ExecLogConvert;
|
||||
import org.dromara.visor.module.asset.convert.TerminalConnectLogConvert;
|
||||
import org.dromara.visor.module.asset.dao.ExecJobDAO;
|
||||
import org.dromara.visor.module.asset.dao.ExecLogDAO;
|
||||
import org.dromara.visor.module.asset.dao.TerminalConnectLogDAO;
|
||||
import org.dromara.visor.framework.redis.core.utils.barrier.CacheBarriers;
|
||||
import org.dromara.visor.module.asset.dao.HostConfigDAO;
|
||||
import org.dromara.visor.module.asset.define.cache.AssetStatisticsCacheKeyDefine;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecJobDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.TerminalConnectLogDO;
|
||||
import org.dromara.visor.module.asset.entity.po.ExecLogCountPO;
|
||||
import org.dromara.visor.module.asset.entity.po.TerminalConnectLogCountPO;
|
||||
import org.dromara.visor.module.asset.entity.vo.AssetWorkplaceStatisticsVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.TerminalConnectLogVO;
|
||||
import org.dromara.visor.module.asset.enums.ExecSourceEnum;
|
||||
import org.dromara.visor.module.asset.entity.po.HostTypeCountPO;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
import org.dromara.visor.module.asset.service.AssetStatisticsService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 资产模块统计服务实现
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/12/23 20:47
|
||||
* @since 2025/3/7 16:26
|
||||
*/
|
||||
@Service
|
||||
public class AssetStatisticsServiceImpl implements AssetStatisticsService {
|
||||
|
||||
@Resource
|
||||
private ExecJobDAO execJobDAO;
|
||||
|
||||
@Resource
|
||||
private ExecLogDAO execLogDAO;
|
||||
|
||||
@Resource
|
||||
private TerminalConnectLogDAO terminalConnectLogDAO;
|
||||
private HostConfigDAO hostConfigDAO;
|
||||
|
||||
@Override
|
||||
public AssetWorkplaceStatisticsVO getWorkplaceStatisticsData() {
|
||||
Long userId = SecurityUtils.getLoginUserId();
|
||||
// 读取缓存
|
||||
String cacheKey = AssetStatisticsCacheKeyDefine.WORKPLACE_DATA.format(userId, Dates.current(Dates.YMD2));
|
||||
AssetWorkplaceStatisticsVO data = RedisStrings.getJson(cacheKey, AssetStatisticsCacheKeyDefine.WORKPLACE_DATA);
|
||||
if (data == null) {
|
||||
// 查询执行的计划任务数量
|
||||
int execJobCount = execJobDAO.of()
|
||||
.createWrapper()
|
||||
.eq(ExecJobDO::getExecUserId, userId)
|
||||
.then()
|
||||
.count()
|
||||
.intValue();
|
||||
// 查询批量执行次数图表
|
||||
LineSingleChartData execLogCountChart = this.getUserExecLogCountChart(userId, ExecSourceEnum.BATCH);
|
||||
List<Integer> execLogCountData = execLogCountChart.getData();
|
||||
int execLogCount = execLogCountData.stream()
|
||||
.mapToInt(Integer::intValue)
|
||||
.sum();
|
||||
// 查询终端连接次数图表
|
||||
LineSingleChartData terminalConnectCountChart = this.getTerminalConnectCountChart(userId);
|
||||
List<Integer> terminalConnectCountData = terminalConnectCountChart.getData();
|
||||
int terminalConnectCount = terminalConnectCountData.stream()
|
||||
.mapToInt(Integer::intValue)
|
||||
.sum();
|
||||
data = AssetWorkplaceStatisticsVO.builder()
|
||||
.execJobCount(execJobCount)
|
||||
.todayExecCommandCount(Lists.last(execLogCountData))
|
||||
.weekExecCommandCount(execLogCount)
|
||||
.todayTerminalConnectCount(Lists.last(terminalConnectCountData))
|
||||
.weekTerminalConnectCount(terminalConnectCount)
|
||||
.execCommandChart(execLogCountChart)
|
||||
.terminalConnectChart(terminalConnectCountChart)
|
||||
.build();
|
||||
public PieChartData getHostTypeChart() {
|
||||
// 查询缓存
|
||||
JSONObject cache = RedisStrings.getJson(AssetStatisticsCacheKeyDefine.HOST_TYPE_COUNT);
|
||||
if (Maps.isEmpty(cache)) {
|
||||
cache = new JSONObject();
|
||||
// 查询数据库
|
||||
List<HostTypeCountPO> typeCountList = hostConfigDAO.selectEnabledTypeCount();
|
||||
for (HostTypeCountPO typeCount : typeCountList) {
|
||||
cache.put(typeCount.getType(), typeCount.getCount());
|
||||
}
|
||||
// 设置屏障 防止穿透
|
||||
CacheBarriers.STRING_MAP.check(cache);
|
||||
// 设置缓存
|
||||
RedisStrings.setJson(cacheKey, AssetStatisticsCacheKeyDefine.WORKPLACE_DATA, data);
|
||||
RedisStrings.set(AssetStatisticsCacheKeyDefine.HOST_TYPE_COUNT, cache);
|
||||
}
|
||||
// 查询命令执行记录
|
||||
List<ExecLogVO> execLogList = execLogDAO.of()
|
||||
.createWrapper()
|
||||
.eq(ExecLogDO::getUserId, userId)
|
||||
.eq(ExecLogDO::getSource, ExecSourceEnum.BATCH.name())
|
||||
.orderByDesc(ExecLogDO::getId)
|
||||
.then()
|
||||
.limit(10)
|
||||
.list(ExecLogConvert.MAPPER::to);
|
||||
data.setExecLogList(execLogList);
|
||||
// 查询终端连接记录
|
||||
List<TerminalConnectLogVO> connectList = terminalConnectLogDAO.of()
|
||||
.createWrapper()
|
||||
.eq(TerminalConnectLogDO::getUserId, userId)
|
||||
.orderByDesc(TerminalConnectLogDO::getId)
|
||||
.then()
|
||||
.limit(10)
|
||||
.list(TerminalConnectLogConvert.MAPPER::to);
|
||||
data.setTerminalConnectList(connectList);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LineSingleChartData getUserExecLogCountChart(Long userId, ExecSourceEnum source) {
|
||||
Date endTime = new Date();
|
||||
Date startTime = Dates.stream()
|
||||
.clearHms()
|
||||
.addDay(-6)
|
||||
.get();
|
||||
List<String> rangeDays = StatisticsRange.WEEK.getDateRanges(startTime);
|
||||
// 查询连接数量
|
||||
Map<String, Integer> countMap = execLogDAO.selectExecLogCount(userId, source.name(), startTime, endTime)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(ExecLogCountPO::getExecDate, ExecLogCountPO::getCount));
|
||||
// 构建每天的数据
|
||||
List<Integer> data = rangeDays.stream()
|
||||
.map(s -> countMap.getOrDefault(s, 0))
|
||||
.collect(Collectors.toList());
|
||||
return LineSingleChartData.builder()
|
||||
.x(rangeDays)
|
||||
.data(data)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LineSingleChartData getTerminalConnectCountChart(Long userId) {
|
||||
Date endTime = new Date();
|
||||
Date startTime = Dates.stream()
|
||||
.clearHms()
|
||||
.addDay(-6)
|
||||
.get();
|
||||
List<String> rangeDays = StatisticsRange.WEEK.getDateRanges(startTime);
|
||||
// 查询连接数量
|
||||
Map<String, Integer> countMap = terminalConnectLogDAO.selectConnectLogUserCount(userId, startTime, endTime)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(TerminalConnectLogCountPO::getConnectDate, TerminalConnectLogCountPO::getCount));
|
||||
// 构建每天的数据
|
||||
List<Integer> data = rangeDays.stream()
|
||||
.map(s -> countMap.getOrDefault(s, 0))
|
||||
.collect(Collectors.toList());
|
||||
return LineSingleChartData.builder()
|
||||
.x(rangeDays)
|
||||
.data(data)
|
||||
.build();
|
||||
// 删除屏障
|
||||
CacheBarriers.STRING_MAP.remove(cache);
|
||||
// 查询类型数量
|
||||
Map<String, Integer> data = new HashMap<>();
|
||||
for (HostTypeEnum value : HostTypeEnum.values()) {
|
||||
data.put(value.name(), Objects1.def(cache.getInteger(value.name()), 0));
|
||||
}
|
||||
return new PieChartData(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.dromara.visor.module.asset.entity.request.host.HostConfigQueryRequest
|
||||
import org.dromara.visor.module.asset.entity.request.host.HostConfigUpdateRequest;
|
||||
import org.dromara.visor.module.asset.enums.HostStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.config.HostConfigStrategyEnum;
|
||||
import org.dromara.visor.module.asset.service.HostConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -78,7 +79,7 @@ public class HostConfigServiceImpl implements HostConfigService {
|
||||
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
|
||||
OperatorLogs.add(OperatorLogs.NAME, host.getName());
|
||||
// 获取处理策略
|
||||
HostTypeEnum strategy = HostTypeEnum.of(type);
|
||||
HostConfigStrategyEnum strategy = HostConfigStrategyEnum.of(type);
|
||||
GenericsDataModel newConfig = strategy.parse(request.getConfig());
|
||||
// 查询配置
|
||||
HostConfigDO record = hostConfigDAO.selectByHostIdType(hostId, type);
|
||||
@@ -123,7 +124,7 @@ public class HostConfigServiceImpl implements HostConfigService {
|
||||
String configValue = originHostConfigMap.get(type);
|
||||
if (Strings.isBlank(configValue)) {
|
||||
// 获取默认值
|
||||
configValue = HostTypeEnum.of(type).getDefault().serial();
|
||||
configValue = HostConfigStrategyEnum.of(type).getDefault().serial();
|
||||
}
|
||||
HostConfigDO newConfig = HostConfigDO.builder()
|
||||
.hostId(newId)
|
||||
@@ -152,7 +153,7 @@ public class HostConfigServiceImpl implements HostConfigService {
|
||||
@Override
|
||||
public <T extends GenericsDataModel> T getHostConfigView(HostConfigQueryRequest request) {
|
||||
String type = request.getType();
|
||||
HostTypeEnum strategy = HostTypeEnum.of(type);
|
||||
HostConfigStrategyEnum strategy = HostConfigStrategyEnum.of(type);
|
||||
// 查询配置
|
||||
HostConfigDO record = hostConfigDAO.selectByHostIdType(request.getHostId(), type);
|
||||
if (record == null) {
|
||||
|
||||
@@ -27,6 +27,10 @@ import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.net.host.SessionStore;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.session.config.BaseConnectConfig;
|
||||
import org.dromara.visor.common.session.config.RdpConnectConfig;
|
||||
import org.dromara.visor.common.session.config.SshConnectConfig;
|
||||
import org.dromara.visor.common.session.ssh.SessionStores;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.module.asset.dao.HostDAO;
|
||||
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
|
||||
@@ -34,12 +38,16 @@ import org.dromara.visor.module.asset.dao.HostKeyDAO;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostIdentityDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostKeyDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostRdpConfigDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.host.HostSshConfigDTO;
|
||||
import org.dromara.visor.module.asset.entity.request.host.HostTestConnectRequest;
|
||||
import org.dromara.visor.module.asset.enums.*;
|
||||
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
|
||||
import org.dromara.visor.module.asset.enums.HostAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostIdentityTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostRdpExtraModel;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostSshExtraModel;
|
||||
import org.dromara.visor.module.asset.handler.host.jsch.SessionStores;
|
||||
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
|
||||
import org.dromara.visor.module.asset.service.HostConfigService;
|
||||
import org.dromara.visor.module.asset.service.HostConnectService;
|
||||
@@ -92,68 +100,93 @@ public class HostConnectServiceImpl implements HostConnectService {
|
||||
// SSH 连接测试
|
||||
SessionStore sessionStore = null;
|
||||
try {
|
||||
TerminalConnectDTO info = this.getSshConnectInfo(id);
|
||||
sessionStore = SessionStores.openSessionStore(info);
|
||||
SshConnectConfig config = this.getSshConnectConfig(id);
|
||||
sessionStore = SessionStores.openSessionStore(config);
|
||||
} catch (Exception e) {
|
||||
throw Exceptions.app(e.getMessage(), e);
|
||||
} finally {
|
||||
Streams.close(sessionStore);
|
||||
}
|
||||
}
|
||||
// TODO: 其他连接方式
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalConnectDTO getSshConnectInfo(Long hostId) {
|
||||
log.info("HostConnectService.getSshConnectInfo-withHost hostId: {}", hostId);
|
||||
public SshConnectConfig getSshConnectConfig(Long hostId) {
|
||||
log.info("HostConnectService.getSshConnectConfig-withHost hostId: {}", hostId);
|
||||
// 查询主机
|
||||
HostDO host = hostDAO.selectById(hostId);
|
||||
// 查询主机配置
|
||||
HostSshConfigModel config = hostConfigService.getHostConfig(hostId, HostTypeEnum.SSH.name());
|
||||
HostSshConfigDTO config = hostConfigService.getHostConfig(hostId, HostTypeEnum.SSH.name());
|
||||
// 获取配置
|
||||
return this.getHostConnectInfo(host, config, null);
|
||||
return this.getSshConnectConfig(host, config, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalConnectDTO getSshConnectInfo(Long hostId, Long userId) {
|
||||
public SshConnectConfig getSshConnectConfig(Long hostId, Long userId) {
|
||||
// 查询主机
|
||||
HostDO host = hostDAO.selectById(hostId);
|
||||
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
|
||||
// 获取配置
|
||||
return this.getSshConnectInfo(host, userId);
|
||||
return this.getSshConnectConfig(host, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalConnectDTO getSshConnectInfo(HostDO host, Long userId) {
|
||||
public SshConnectConfig getSshConnectConfig(HostDO host, Long userId) {
|
||||
Long hostId = host.getId();
|
||||
log.info("HostConnectService.getSshConnectInfo hostId: {}, userId: {}", hostId, userId);
|
||||
// 验证主机是否有权限
|
||||
List<Long> hostIdList = assetAuthorizedDataService.getUserAuthorizedHostId(userId);
|
||||
Valid.isTrue(hostIdList.contains(hostId),
|
||||
ErrorMessage.ANY_NO_PERMISSION,
|
||||
DataPermissionTypeEnum.HOST_GROUP.getPermissionName());
|
||||
log.info("HostConnectService.getSshConnectConfig hostId: {}, userId: {}", hostId, userId);
|
||||
// 验证权限
|
||||
this.validHostAuthorized(userId, hostId);
|
||||
// 获取主机配置
|
||||
HostSshConfigModel config = hostConfigService.getHostConfig(hostId, HostTypeEnum.SSH.name());
|
||||
HostSshConfigDTO config = hostConfigService.getHostConfig(hostId, HostTypeEnum.SSH.name());
|
||||
Valid.notNull(config, ErrorMessage.CONFIG_ABSENT);
|
||||
// 查询主机额外配置
|
||||
HostSshExtraModel extra = hostExtraService.getHostExtra(userId, hostId, HostExtraItemEnum.SSH);
|
||||
if (extra != null) {
|
||||
HostExtraSshAuthTypeEnum extraAuthType = HostExtraSshAuthTypeEnum.of(extra.getAuthType());
|
||||
if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(extraAuthType)) {
|
||||
// 验证主机密钥是否有权限
|
||||
Valid.notNull(extra.getKeyId(), ErrorMessage.KEY_ABSENT);
|
||||
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_KEY, userId, extra.getKeyId()),
|
||||
ErrorMessage.ANY_NO_PERMISSION,
|
||||
DataPermissionTypeEnum.HOST_KEY.getPermissionName());
|
||||
} else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
|
||||
// 验证主机身份是否有权限
|
||||
Valid.notNull(extra.getIdentityId(), ErrorMessage.IDENTITY_ABSENT);
|
||||
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_IDENTITY, userId, extra.getIdentityId()),
|
||||
ErrorMessage.ANY_NO_PERMISSION,
|
||||
DataPermissionTypeEnum.HOST_IDENTITY.getPermissionName());
|
||||
}
|
||||
// 验证额外认证方式
|
||||
this.validExtraAuthentication(userId, extra.getAuthType(), extra.getKeyId(), extra.getIdentityId());
|
||||
}
|
||||
// 获取连接配置
|
||||
return this.getHostConnectInfo(host, config, extra);
|
||||
return this.getSshConnectConfig(host, config, extra);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RdpConnectConfig getRdpConnectConfig(Long hostId) {
|
||||
log.info("HostConnectService.getRdpConnectConfig-withHost hostId: {}", hostId);
|
||||
// 查询主机
|
||||
HostDO host = hostDAO.selectById(hostId);
|
||||
// 查询主机配置
|
||||
HostRdpConfigDTO config = hostConfigService.getHostConfig(hostId, HostTypeEnum.RDP.name());
|
||||
// 获取配置
|
||||
return this.getRdpConnectConfig(host, config, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RdpConnectConfig getRdpConnectConfig(Long hostId, Long userId) {
|
||||
// 查询主机
|
||||
HostDO host = hostDAO.selectById(hostId);
|
||||
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
|
||||
// 获取配置
|
||||
return this.getRdpConnectConfig(host, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RdpConnectConfig getRdpConnectConfig(HostDO host, Long userId) {
|
||||
Long hostId = host.getId();
|
||||
log.info("HostConnectService.getRdpConnectConfig hostId: {}, userId: {}", hostId, userId);
|
||||
// 验证权限
|
||||
this.validHostAuthorized(userId, hostId);
|
||||
// 获取主机配置
|
||||
HostRdpConfigDTO config = hostConfigService.getHostConfig(hostId, HostTypeEnum.RDP.name());
|
||||
Valid.notNull(config, ErrorMessage.CONFIG_ABSENT);
|
||||
// 查询主机额外配置
|
||||
HostRdpExtraModel extra = hostExtraService.getHostExtra(userId, hostId, HostExtraItemEnum.RDP);
|
||||
if (extra != null) {
|
||||
// 验证额外认证方式
|
||||
this.validExtraAuthentication(userId, extra.getAuthType(), null, extra.getIdentityId());
|
||||
}
|
||||
// 获取连接配置
|
||||
return this.getRdpConnectConfig(host, config, extra);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,46 +195,41 @@ public class HostConnectServiceImpl implements HostConnectService {
|
||||
* @param host host
|
||||
* @param config config
|
||||
* @param extra extra
|
||||
* @return session
|
||||
* @return info
|
||||
*/
|
||||
private TerminalConnectDTO getHostConnectInfo(HostDO host,
|
||||
HostSshConfigModel config,
|
||||
HostSshExtraModel extra) {
|
||||
// 填充认证信息
|
||||
TerminalConnectDTO conn = new TerminalConnectDTO();
|
||||
conn.setOsType(host.getOsType());
|
||||
conn.setArchType(host.getArchType());
|
||||
conn.setHostId(host.getId());
|
||||
conn.setHostName(host.getName());
|
||||
conn.setHostCode(host.getCode());
|
||||
conn.setHostAddress(host.getAddress());
|
||||
conn.setHostPort(config.getPort());
|
||||
conn.setTimeout(config.getConnectTimeout());
|
||||
conn.setCharset(config.getCharset());
|
||||
conn.setFileNameCharset(config.getFileNameCharset());
|
||||
conn.setFileContentCharset(config.getFileContentCharset());
|
||||
|
||||
private SshConnectConfig getSshConnectConfig(HostDO host,
|
||||
HostSshConfigDTO config,
|
||||
HostSshExtraModel extra) {
|
||||
SshConnectConfig connectConfig = SshConnectConfig.builder()
|
||||
.hostPort(config.getPort())
|
||||
.timeout(config.getConnectTimeout())
|
||||
.charset(config.getCharset())
|
||||
.fileNameCharset(config.getFileNameCharset())
|
||||
.fileContentCharset(config.getFileContentCharset())
|
||||
.build();
|
||||
// 填充基础主机信息
|
||||
this.setBaseConnectConfig(connectConfig, host);
|
||||
// 获取自定义认证方式
|
||||
HostExtraSshAuthTypeEnum extraAuthType = Optional.ofNullable(extra)
|
||||
HostExtraAuthTypeEnum extraAuthType = Optional.ofNullable(extra)
|
||||
.map(HostSshExtraModel::getAuthType)
|
||||
.map(HostExtraSshAuthTypeEnum::of)
|
||||
.map(HostExtraAuthTypeEnum::of)
|
||||
.orElse(null);
|
||||
if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(extraAuthType)) {
|
||||
if (HostExtraAuthTypeEnum.CUSTOM_KEY.equals(extraAuthType)) {
|
||||
// 自定义密钥
|
||||
config.setAuthType(HostSshAuthTypeEnum.KEY.name());
|
||||
config.setAuthType(HostAuthTypeEnum.KEY.name());
|
||||
config.setKeyId(extra.getKeyId());
|
||||
if (extra.getUsername() != null) {
|
||||
config.setUsername(extra.getUsername());
|
||||
}
|
||||
} else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
|
||||
} else if (HostExtraAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
|
||||
// 自定义身份
|
||||
config.setAuthType(HostSshAuthTypeEnum.IDENTITY.name());
|
||||
config.setAuthType(HostAuthTypeEnum.IDENTITY.name());
|
||||
config.setIdentityId(extra.getIdentityId());
|
||||
}
|
||||
|
||||
// 身份认证
|
||||
HostSshAuthTypeEnum authType = HostSshAuthTypeEnum.of(config.getAuthType());
|
||||
if (HostSshAuthTypeEnum.IDENTITY.equals(authType)) {
|
||||
HostAuthTypeEnum authType = HostAuthTypeEnum.of(config.getAuthType());
|
||||
if (HostAuthTypeEnum.IDENTITY.equals(authType)) {
|
||||
// 身份认证
|
||||
Valid.notNull(config.getIdentityId(), ErrorMessage.IDENTITY_ABSENT);
|
||||
HostIdentityDO identity = hostIdentityDAO.selectById(config.getIdentityId());
|
||||
@@ -210,32 +238,146 @@ public class HostConnectServiceImpl implements HostConnectService {
|
||||
HostIdentityTypeEnum identityType = HostIdentityTypeEnum.of(identity.getType());
|
||||
if (HostIdentityTypeEnum.PASSWORD.equals(identityType)) {
|
||||
// 密码类型
|
||||
authType = HostSshAuthTypeEnum.PASSWORD;
|
||||
authType = HostAuthTypeEnum.PASSWORD;
|
||||
config.setPassword(identity.getPassword());
|
||||
} else if (HostIdentityTypeEnum.KEY.equals(identityType)) {
|
||||
// 密钥类型
|
||||
authType = HostSshAuthTypeEnum.KEY;
|
||||
authType = HostAuthTypeEnum.KEY;
|
||||
config.setKeyId(identity.getKeyId());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充认证信息
|
||||
conn.setUsername(config.getUsername());
|
||||
if (HostSshAuthTypeEnum.PASSWORD.equals(authType)) {
|
||||
connectConfig.setUsername(config.getUsername());
|
||||
if (HostAuthTypeEnum.PASSWORD.equals(authType)) {
|
||||
// 密码认证
|
||||
conn.setPassword(config.getPassword());
|
||||
} else if (HostSshAuthTypeEnum.KEY.equals(authType)) {
|
||||
connectConfig.setPassword(config.getPassword());
|
||||
} else if (HostAuthTypeEnum.KEY.equals(authType)) {
|
||||
// 密钥认证
|
||||
Long keyId = config.getKeyId();
|
||||
Valid.notNull(keyId, ErrorMessage.KEY_ABSENT);
|
||||
HostKeyDO key = hostKeyDAO.selectById(keyId);
|
||||
Valid.notNull(key, ErrorMessage.KEY_ABSENT);
|
||||
conn.setKeyId(keyId);
|
||||
conn.setPublicKey(key.getPublicKey());
|
||||
conn.setPrivateKey(key.getPrivateKey());
|
||||
conn.setPrivateKeyPassword(key.getPassword());
|
||||
connectConfig.setKeyId(keyId);
|
||||
connectConfig.setPublicKey(key.getPublicKey());
|
||||
connectConfig.setPrivateKey(key.getPrivateKey());
|
||||
connectConfig.setPrivateKeyPassword(key.getPassword());
|
||||
}
|
||||
return conn;
|
||||
return connectConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 RDP 连接信息
|
||||
*
|
||||
* @param host host
|
||||
* @param config config
|
||||
* @return info
|
||||
*/
|
||||
private RdpConnectConfig getRdpConnectConfig(HostDO host,
|
||||
HostRdpConfigDTO config,
|
||||
HostRdpExtraModel extra) {
|
||||
// 填充认证信息
|
||||
RdpConnectConfig connectConfig = RdpConnectConfig.builder()
|
||||
.hostPort(config.getPort())
|
||||
.versionGt81(config.getVersionGt81())
|
||||
.timezone(config.getTimezone())
|
||||
.keyboardLayout(config.getKeyboardLayout())
|
||||
.clipboardNormalize(config.getClipboardNormalize())
|
||||
.domain(config.getDomain())
|
||||
.preConnectionId(config.getPreConnectionId())
|
||||
.preConnectionBlob(config.getPreConnectionBlob())
|
||||
.remoteApp(config.getRemoteApp())
|
||||
.remoteAppDir(config.getRemoteAppDir())
|
||||
.remoteAppArgs(config.getRemoteAppArgs())
|
||||
.build();
|
||||
// 填充基础主机信息
|
||||
this.setBaseConnectConfig(connectConfig, host);
|
||||
if (extra != null) {
|
||||
// 设置低带宽模式
|
||||
connectConfig.setLowBandwidthMode(extra.getLowBandwidthMode());
|
||||
// 获取自定义认证方式
|
||||
HostExtraAuthTypeEnum extraAuthType = HostExtraAuthTypeEnum.of(extra.getAuthType());
|
||||
if (HostExtraAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
|
||||
// 自定义身份
|
||||
config.setAuthType(HostAuthTypeEnum.IDENTITY.name());
|
||||
config.setIdentityId(extra.getIdentityId());
|
||||
}
|
||||
}
|
||||
|
||||
// 身份认证
|
||||
HostAuthTypeEnum authType = HostAuthTypeEnum.of(config.getAuthType());
|
||||
if (HostAuthTypeEnum.IDENTITY.equals(authType)) {
|
||||
// 身份认证 - 仅密码
|
||||
authType = HostAuthTypeEnum.PASSWORD;
|
||||
Valid.notNull(config.getIdentityId(), ErrorMessage.IDENTITY_ABSENT);
|
||||
HostIdentityDO identity = hostIdentityDAO.selectById(config.getIdentityId());
|
||||
Valid.notNull(identity, ErrorMessage.IDENTITY_ABSENT);
|
||||
// 设置身份信息
|
||||
config.setUsername(identity.getUsername());
|
||||
config.setPassword(identity.getPassword());
|
||||
}
|
||||
|
||||
// 填充认证信息
|
||||
connectConfig.setUsername(config.getUsername());
|
||||
if (HostAuthTypeEnum.PASSWORD.equals(authType)) {
|
||||
// 密码认证
|
||||
connectConfig.setPassword(config.getPassword());
|
||||
}
|
||||
return connectConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证主机权限
|
||||
*
|
||||
* @param userId userId
|
||||
* @param hostId hostId
|
||||
*/
|
||||
private void validHostAuthorized(Long userId, Long hostId) {
|
||||
// 验证主机是否有权限
|
||||
List<Long> hostIdList = assetAuthorizedDataService.getUserAuthorizedHostId(userId);
|
||||
Valid.isTrue(hostIdList.contains(hostId),
|
||||
ErrorMessage.ANY_NO_PERMISSION,
|
||||
DataPermissionTypeEnum.HOST_GROUP.getPermissionName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证额外认证方式
|
||||
*
|
||||
* @param userId userId
|
||||
* @param authType authType
|
||||
* @param keyId keyId
|
||||
* @param identityId identityId
|
||||
*/
|
||||
private void validExtraAuthentication(Long userId, String authType, Long keyId, Long identityId) {
|
||||
HostExtraAuthTypeEnum extraAuthType = HostExtraAuthTypeEnum.of(authType);
|
||||
if (HostExtraAuthTypeEnum.CUSTOM_KEY.equals(extraAuthType)) {
|
||||
// 验证主机密钥是否有权限
|
||||
Valid.notNull(keyId, ErrorMessage.KEY_ABSENT);
|
||||
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_KEY, userId, keyId),
|
||||
ErrorMessage.ANY_NO_PERMISSION,
|
||||
DataPermissionTypeEnum.HOST_KEY.getPermissionName());
|
||||
} else if (HostExtraAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
|
||||
// 验证主机身份是否有权限
|
||||
Valid.notNull(identityId, ErrorMessage.IDENTITY_ABSENT);
|
||||
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_IDENTITY, userId, identityId),
|
||||
ErrorMessage.ANY_NO_PERMISSION,
|
||||
DataPermissionTypeEnum.HOST_IDENTITY.getPermissionName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置基础主机信息
|
||||
*
|
||||
* @param config config
|
||||
* @param host host
|
||||
*/
|
||||
private void setBaseConnectConfig(BaseConnectConfig config, HostDO host) {
|
||||
config.setOsType(host.getOsType());
|
||||
config.setArchType(host.getArchType());
|
||||
config.setHostId(host.getId());
|
||||
config.setHostName(host.getName());
|
||||
config.setHostCode(host.getCode());
|
||||
config.setHostAddress(host.getAddress());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
|
||||
import org.dromara.visor.module.asset.entity.request.host.HostExtraUpdateRequest;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostSpecExtraModel;
|
||||
import org.dromara.visor.module.asset.service.HostExtraService;
|
||||
import org.dromara.visor.module.infra.api.DataExtraApi;
|
||||
|
||||
@@ -298,8 +298,8 @@ public class HostIdentityServiceImpl implements HostIdentityService {
|
||||
return hostIdentityDAO.wrapper()
|
||||
.eq(HostIdentityDO::getId, request.getId())
|
||||
.eq(HostIdentityDO::getType, request.getType())
|
||||
.eq(HostIdentityDO::getKeyId, request.getKeyId())
|
||||
.like(HostIdentityDO::getName, request.getName())
|
||||
.eq(HostIdentityDO::getKeyId, request.getKeyId())
|
||||
.like(HostIdentityDO::getUsername, request.getUsername())
|
||||
.like(HostIdentityDO::getDescription, request.getDescription())
|
||||
.and(Strings.isNotEmpty(searchValue), c -> c
|
||||
|
||||
@@ -214,7 +214,7 @@ public class HostKeyServiceImpl implements HostKeyService {
|
||||
OperatorLogs.add(OperatorLogs.NAME, name);
|
||||
// 删除数据库
|
||||
int effect = hostKeyDAO.deleteBatchIds(idList);
|
||||
// 删除关联
|
||||
// 删除身份关联
|
||||
hostIdentityDAO.setKeyWithNull(idList);
|
||||
// 删除主机配置
|
||||
hostConfigDAO.setKeyIdWithNull(idList);
|
||||
|
||||
@@ -37,19 +37,25 @@ import org.dromara.visor.common.enums.EnableStatus;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.framework.redis.core.utils.RedisMaps;
|
||||
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
|
||||
import org.dromara.visor.framework.redis.core.utils.barrier.CacheBarriers;
|
||||
import org.dromara.visor.module.asset.convert.HostConvert;
|
||||
import org.dromara.visor.module.asset.dao.HostConfigDAO;
|
||||
import org.dromara.visor.module.asset.dao.HostDAO;
|
||||
import org.dromara.visor.module.asset.define.cache.AssetStatisticsCacheKeyDefine;
|
||||
import org.dromara.visor.module.asset.define.cache.HostCacheKeyDefine;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.HostCacheDTO;
|
||||
import org.dromara.visor.module.asset.entity.request.host.*;
|
||||
import org.dromara.visor.module.asset.entity.vo.HostVO;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostSpecExtraModel;
|
||||
import org.dromara.visor.module.asset.service.*;
|
||||
import org.dromara.visor.module.asset.service.HostConfigService;
|
||||
import org.dromara.visor.module.asset.service.HostExtraService;
|
||||
import org.dromara.visor.module.asset.service.HostService;
|
||||
import org.dromara.visor.module.exec.api.ExecJobApi;
|
||||
import org.dromara.visor.module.exec.api.ExecTemplateApi;
|
||||
import org.dromara.visor.module.infra.api.DataExtraApi;
|
||||
import org.dromara.visor.module.infra.api.DataGroupRelApi;
|
||||
import org.dromara.visor.module.infra.api.FavoriteApi;
|
||||
@@ -95,10 +101,10 @@ public class HostServiceImpl implements HostService {
|
||||
private HostExtraService hostExtraService;
|
||||
|
||||
@Resource
|
||||
private ExecJobHostService execJobHostService;
|
||||
private ExecJobApi execJobApi;
|
||||
|
||||
@Resource
|
||||
private ExecTemplateHostService execTemplateHostService;
|
||||
private ExecTemplateApi execTemplateApi;
|
||||
|
||||
@Resource
|
||||
private TagRelApi tagRelApi;
|
||||
@@ -324,9 +330,9 @@ public class HostServiceImpl implements HostService {
|
||||
// 删除主机配置
|
||||
hostConfigDAO.deleteByHostIdList(idList);
|
||||
// 删除计划任务主机
|
||||
execJobHostService.deleteByHostIdList(idList);
|
||||
execJobApi.deleteByHostIdList(idList);
|
||||
// 删除执行模板主机
|
||||
execTemplateHostService.deleteByHostIdList(idList);
|
||||
execTemplateApi.deleteByHostIdList(idList);
|
||||
// 删除分组
|
||||
dataGroupRelApi.deleteByRelIdList(DataGroupTypeEnum.HOST, idList);
|
||||
// 删除 tag 引用
|
||||
@@ -340,6 +346,7 @@ public class HostServiceImpl implements HostService {
|
||||
@Override
|
||||
public void clearCache() {
|
||||
RedisMaps.scanKeysDelete(HostCacheKeyDefine.HOST_INFO.format("*"));
|
||||
RedisStrings.delete(AssetStatisticsCacheKeyDefine.HOST_TYPE_COUNT.getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
<result column="deleted" property="deleted"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 类型数量结果映射 -->
|
||||
<resultMap id="TypeCountResultMap" type="org.dromara.visor.module.asset.entity.po.HostTypeCountPO">
|
||||
<result column="type" property="type"/>
|
||||
<result column="total_count" property="count"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, host_id, type, status, config, create_time, update_time, creator, updater, deleted
|
||||
@@ -39,4 +45,12 @@
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<select id="selectEnabledTypeCount" resultMap="TypeCountResultMap">
|
||||
SELECT type, COUNT(1) total_count
|
||||
FROM host_config
|
||||
WHERE deleted = 0
|
||||
AND status = 'ENABLED'
|
||||
GROUP BY type
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
*/
|
||||
package org.dromara.visor.module.common.utils;
|
||||
|
||||
import org.dromara.visor.module.common.config.AppSftpConfig;
|
||||
import org.dromara.visor.module.common.entity.dto.SftpFileBackupDTO;
|
||||
import cn.orionsec.kit.lang.constant.Letters;
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
@@ -32,6 +30,8 @@ import cn.orionsec.kit.net.host.sftp.SftpExecutor;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpFile;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.dromara.visor.module.common.config.AppSftpConfig;
|
||||
import org.dromara.visor.module.common.entity.dto.SftpFileBackupDTO;
|
||||
|
||||
/**
|
||||
* sftp 工具类
|
||||
|
||||
Reference in New Issue
Block a user