refactor: 初步提交全局授权凭证-资源多账号改造

This commit is contained in:
meilin.huang
2024-04-09 12:55:51 +08:00
parent 408bac09a1
commit 21498584b1
59 changed files with 1779 additions and 656 deletions

View File

@@ -1,21 +1,27 @@
package api
import (
"mayfly-go/internal/common/consts"
"mayfly-go/internal/machine/application"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
)
type Dashbord struct {
TagTreeApp tagapp.TagTree `inject:""`
MachineApp application.Machine `inject:""`
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
MachineApp application.Machine `inject:""`
}
func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id
machienNum := len(m.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMachine, ""))
machienAuthCerts := m.ResourceAuthCertApp.GetAccountAuthCert(accountId, tagentity.TagTypeMachineAuthCert)
machineCodes := collx.ArrayMap(machienAuthCerts, func(ac *tagentity.ResourceAuthCert) string {
return ac.ResourceCode
})
machienNum := len(collx.ArrayDeduplicate(machineCodes))
rc.ResData = collx.M{
"machineNum": machienNum,

View File

@@ -1,5 +1,7 @@
package form
import tagentity "mayfly-go/internal/tag/domain/entity"
type MachineForm struct {
Id uint64 `json:"id"`
Protocol int `json:"protocol" binding:"required"`
@@ -8,11 +10,8 @@ type MachineForm struct {
Ip string `json:"ip" binding:"required"` // IP地址
Port int `json:"port" binding:"required"` // 端口号
// 资产授权凭证信息列表
AuthCertId int `json:"authCertId"`
TagId []uint64 `json:"tagId" binding:"required"`
Username string `json:"username"`
Password string `json:"password"`
TagId []uint64 `json:"tagId" binding:"required"`
AuthCerts []*tagentity.ResourceAuthCert // 资产授权凭证信息列表
Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
@@ -24,14 +23,6 @@ type MachineRunForm struct {
Cmd string `json:"cmd" binding:"required"`
}
type MachineFileForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
MachineId uint64 `json:"machineId" binding:"required"`
Type int `json:"type" binding:"required"`
Path string `json:"path" binding:"required"`
}
type MachineScriptForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
@@ -42,39 +33,6 @@ type MachineScriptForm struct {
Script string `json:"script" binding:"required"`
}
type ServerFileOptionForm struct {
MachineId uint64 `form:"machineId"`
Protocol int `form:"protocol"`
Path string `form:"path"`
Type string `form:"type"`
Content string `form:"content"`
Id uint64 `form:"id"`
FileId uint64 `form:"fileId"`
}
type MachineFileUpdateForm struct {
Content string `json:"content" binding:"required"`
Id uint64 `json:"id" binding:"required"`
Path string `json:"path" binding:"required"`
}
type MachineFileOpForm struct {
Path []string `json:"path" binding:"required"`
ToPath string `json:"toPath"`
MachineId uint64 `json:"machineId" binding:"required"`
Protocol int `json:"protocol" binding:"required"`
FileId uint64 `json:"fileId" binding:"required"`
}
type MachineFileRename struct {
MachineId uint64 `json:"machineId" binding:"required"`
Protocol int `json:"protocol" binding:"required"`
FileId uint64 `json:"fileId" binding:"required"`
Oldname string `json:"oldname" binding:"required"`
Newname string `json:"newname" binding:"required"`
}
// 授权凭证
type AuthCertForm struct {
Id uint64 `json:"id"`

View File

@@ -0,0 +1,48 @@
package form
import "mayfly-go/internal/machine/application"
type MachineFileForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
MachineId uint64 `json:"machineId" binding:"required"`
Type int `json:"type" binding:"required"`
Path string `json:"path" binding:"required"`
}
type MachineFileUpdateForm struct {
Content string `json:"content" binding:"required"`
Id uint64 `json:"id" binding:"required"`
Path string `json:"path" binding:"required"`
}
type CreateFileForm struct {
*application.MachineFileOpParam
Type string `json:"type"`
}
type WriteFileContentForm struct {
*application.MachineFileOpParam
Content string `json:"content" binding:"required"`
}
type RemoveFileForm struct {
*application.MachineFileOpParam
Paths []string `json:"paths" binding:"required"`
}
type CopyFileForm struct {
*application.MachineFileOpParam
Paths []string `json:"paths" binding:"required"`
ToPath string `json:"toPath" binding:"required"`
}
type RenameForm struct {
*application.MachineFileOpParam
Newname string `json:"newname" binding:"required"`
}

View File

@@ -3,10 +3,6 @@ package api
import (
"encoding/base64"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/may-fly/cast"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/machine/api/form"
"mayfly-go/internal/machine/api/vo"
"mayfly-go/internal/machine/application"
@@ -29,24 +25,33 @@ import (
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/may-fly/cast"
)
type Machine struct {
MachineApp application.Machine `inject:""`
MachineTermOpApp application.MachineTermOp `inject:""`
TagApp tagapp.TagTree `inject:"TagTreeApp"`
MachineApp application.Machine `inject:""`
MachineTermOpApp application.MachineTermOp `inject:""`
TagApp tagapp.TagTree `inject:"TagTreeApp"`
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
}
func (m *Machine) Machines(rc *req.Ctx) {
condition, pageParam := req.BindQueryAndPage(rc, new(entity.MachineQuery))
// 不存在可访问标签id即没有可操作数据
codes := m.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeMachine, condition.TagPath)
if len(codes) == 0 {
authCerts := m.ResourceAuthCertApp.GetAccountAuthCert(rc.GetLoginAccount().Id, tagentity.TagTypeMachineAuthCert, condition.TagPath)
// 不存在可操作的授权凭证,即没有可操作数据
if len(authCerts) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
condition.Codes = codes
machineCodes := collx.ArrayMap(authCerts, func(ac *tagentity.ResourceAuthCert) string {
return ac.ResourceCode
})
condition.Codes = collx.ArrayDeduplicate(machineCodes)
var machinevos []*vo.MachineVO
res, err := m.MachineApp.GetMachineList(condition, pageParam, &machinevos)
@@ -61,6 +66,11 @@ func (m *Machine) Machines(rc *req.Ctx) {
return mvo
})...)
// 填充授权凭证信息
m.ResourceAuthCertApp.FillAuthCert(authCerts, collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
return mvo
})...)
for _, mv := range machinevos {
if machineStats, err := m.MachineApp.GetMachineStats(mv.Id); err == nil {
mv.Stat = collx.M{
@@ -85,16 +95,20 @@ func (m *Machine) SaveMachine(rc *req.Ctx) {
machineForm := new(form.MachineForm)
me := req.BindJsonAndCopyTo(rc, machineForm, new(entity.Machine))
machineForm.Password = "******"
rc.ReqParam = machineForm
biz.ErrIsNil(m.MachineApp.SaveMachine(rc.MetaCtx, me, machineForm.TagId...))
biz.ErrIsNil(m.MachineApp.SaveMachine(rc.MetaCtx, &application.SaveMachineParam{
Machine: me,
TagIds: machineForm.TagId,
AuthCerts: machineForm.AuthCerts,
}))
}
func (m *Machine) TestConn(rc *req.Ctx) {
me := req.BindJsonAndCopyTo(rc, new(form.MachineForm), new(entity.Machine))
machineForm := new(form.MachineForm)
me := req.BindJsonAndCopyTo(rc, machineForm, new(entity.Machine))
// 测试连接
biz.ErrIsNilAppendErr(m.MachineApp.TestConn(me), "该机器无法连接: %s")
biz.ErrIsNilAppendErr(m.MachineApp.TestConn(me, machineForm.AuthCerts[0]), "该机器无法连接: %s")
}
func (m *Machine) ChangeStatus(rc *req.Ctx) {
@@ -175,7 +189,7 @@ func (m *Machine) WsSSH(g *gin.Context) {
panic(errorx.NewBiz("\033[1;31m您没有权限操作该机器终端,请重新登录后再试~\033[0m"))
}
cli, err := m.MachineApp.NewCli(GetMachineId(rc))
cli, err := m.MachineApp.NewCli(GetMachineAc(rc))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
defer cli.Close()
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath...), "%s")
@@ -231,9 +245,9 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
biz.ErrIsNil(err)
rc := req.NewCtxWithGin(g).WithRequiredPermission(req.NewPermission("machine:terminal"))
machineId := GetMachineId(rc)
ac := GetMachineAc(rc)
mi, err := m.MachineApp.ToMachineInfoById(machineId)
mi, err := m.MachineApp.ToMachineInfoByAc(ac)
if err != nil {
return
}
@@ -258,7 +272,7 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
if mi.EnableRecorder == 1 {
// 操作记录 查看文档https://guacamole.apache.org/doc/gug/configuring-guacamole.html#graphical-recording
params["recording-path"] = fmt.Sprintf("/rdp-rec/%d", machineId)
params["recording-path"] = fmt.Sprintf("/rdp-rec/%s", ac)
params["create-recording-path"] = "true"
params["recording-include-keys"] = "true"
}
@@ -273,14 +287,14 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
if query.Get("force") != "" {
// 判断是否强制连接,是的话,查询是否有正在连接的会话,有的话强制关闭
if cast.ToBool(query.Get("force")) {
tn := sessions.Get(machineId)
tn := sessions.Get(ac)
if tn != nil {
_ = tn.Close()
}
}
}
tunnel, err := guac.DoConnect(query, params, machineId)
tunnel, err := guac.DoConnect(query, params, ac)
if err != nil {
return
}
@@ -290,9 +304,9 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
}
}()
sessions.Add(machineId, wsConn, g.Request, tunnel)
sessions.Add(ac, wsConn, g.Request, tunnel)
defer sessions.Delete(machineId, wsConn, g.Request, tunnel)
defer sessions.Delete(ac, wsConn, g.Request, tunnel)
writer := tunnel.AcquireWriter()
reader := tunnel.AcquireReader()
@@ -312,3 +326,9 @@ func GetMachineId(rc *req.Ctx) uint64 {
biz.IsTrue(machineId != 0, "machineId错误")
return uint64(machineId)
}
func GetMachineAc(rc *req.Ctx) string {
ac := rc.PathParam("ac")
biz.IsTrue(ac != "", "authCertName错误")
return ac
}

View File

@@ -23,9 +23,10 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/may-fly/cast"
)
type MachineFile struct {
@@ -62,8 +63,7 @@ func (m *MachineFile) DeleteFile(rc *req.Ctx) {
/*** sftp相关操作 */
func (m *MachineFile) CreateFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.ServerFileOptionForm))
opForm := req.BindJsonAndValid(rc, new(form.CreateFileForm))
path := opForm.Path
attrs := collx.Kvs("path", path)
@@ -71,10 +71,10 @@ func (m *MachineFile) CreateFile(rc *req.Ctx) {
var err error
if opForm.Type == dir {
attrs["type"] = "目录"
mi, err = m.MachineFileApp.MkDir(opForm.FileId, opForm.Path, opForm)
mi, err = m.MachineFileApp.MkDir(opForm.MachineFileOpParam)
} else {
attrs["type"] = "文件"
mi, err = m.MachineFileApp.CreateFile(opForm.FileId, opForm.Path, opForm)
mi, err = m.MachineFileApp.CreateFile(opForm.MachineFileOpParam)
}
attrs["machine"] = mi
rc.ReqParam = attrs
@@ -82,7 +82,7 @@ func (m *MachineFile) CreateFile(rc *req.Ctx) {
}
func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
readPath := opForm.Path
// 特殊处理rdp文件
if opForm.Protocol == entity.MachineProtocolRdp {
@@ -96,7 +96,7 @@ func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
return
}
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm.FileId, readPath)
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm)
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
defer sftpFile.Close()
@@ -112,7 +112,7 @@ func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
}
func (m *MachineFile) DownloadFile(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
readPath := opForm.Path
@@ -131,7 +131,7 @@ func (m *MachineFile) DownloadFile(rc *req.Ctx) {
return
}
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm.FileId, readPath)
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm)
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
defer sftpFile.Close()
@@ -140,11 +140,11 @@ func (m *MachineFile) DownloadFile(rc *req.Ctx) {
}
func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
readPath := opForm.Path
rc.ReqParam = fmt.Sprintf("path: %s", readPath)
fis, err := m.MachineFileApp.ReadDir(opForm.FileId, opForm)
fis, err := m.MachineFileApp.ReadDir(opForm)
biz.ErrIsNilAppendErr(err, "读取目录失败: %s")
fisVO := make([]vo.MachineFileInfo, 0)
@@ -173,34 +173,34 @@ func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
}
func (m *MachineFile) GetDirSize(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
size, err := m.MachineFileApp.GetDirSize(opForm.FileId, opForm)
size, err := m.MachineFileApp.GetDirSize(opForm)
biz.ErrIsNil(err)
rc.ResData = size
}
func (m *MachineFile) GetFileStat(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
res, err := m.MachineFileApp.FileStat(opForm)
biz.ErrIsNil(err, res)
rc.ResData = res
}
func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindJsonAndValid(rc, new(form.WriteFileContentForm))
path := opForm.Path
mi, err := m.MachineFileApp.WriteFileContent(opForm.FileId, path, []byte(opForm.Content), opForm)
mi, err := m.MachineFileApp.WriteFileContent(opForm.MachineFileOpParam, []byte(opForm.Content))
rc.ReqParam = collx.Kvs("machine", mi, "path", path)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
}
func (m *MachineFile) UploadFile(rc *req.Ctx) {
fid := GetMachineFileId(rc)
path := rc.PostForm("path")
protocol, err := strconv.Atoi(rc.PostForm("protocol"))
machineId, err := strconv.Atoi(rc.PostForm("machineId"))
protocol := cast.ToInt(rc.PostForm("protocol"))
machineId := cast.ToUint64(rc.PostForm("machineId"))
authCertName := rc.PostForm("authCertName")
fileheader, err := rc.FormFile("file")
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
@@ -219,14 +219,14 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
}
}()
opForm := &form.ServerFileOptionForm{
FileId: fid,
MachineId: uint64(machineId),
Protocol: protocol,
Path: path,
opForm := &application.MachineFileOpParam{
MachineId: machineId,
AuthCertName: authCertName,
Protocol: protocol,
Path: path,
}
mi, err := m.MachineFileApp.UploadFile(fid, path, fileheader.Filename, file, opForm)
mi, err := m.MachineFileApp.UploadFile(opForm, fileheader.Filename, file)
rc.ReqParam = collx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", path, fileheader.Filename))
biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
// 保存消息并发送文件上传成功通知
@@ -239,8 +239,6 @@ type FolderFile struct {
}
func (m *MachineFile) UploadFolder(rc *req.Ctx) {
fid := GetMachineFileId(rc)
mf, err := rc.MultipartForm()
biz.ErrIsNilAppendErr(err, "获取表单信息失败: %s")
basePath := mf.Value["basePath"][0]
@@ -256,21 +254,24 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
biz.IsTrue(allFileSize <= maxUploadFileSize, "文件夹总大小不能超过%d字节", maxUploadFileSize)
paths := mf.Value["paths"]
authCertName := mf.Value["authCertName"][0]
machineId := cast.ToUint64(mf.Value["machineId"][0])
// protocol
protocol, err := strconv.Atoi(mf.Value["protocol"][0])
protocol := cast.ToInt(mf.Value["protocol"][0])
opForm := &application.MachineFileOpParam{
MachineId: machineId,
Protocol: protocol,
AuthCertName: authCertName,
}
if protocol == entity.MachineProtocolRdp {
machineId, _ := strconv.Atoi(mf.Value["machineId"][0])
opForm := &form.ServerFileOptionForm{
MachineId: uint64(machineId),
Protocol: protocol,
}
m.MachineFileApp.UploadFiles(basePath, fileheaders, paths, opForm)
m.MachineFileApp.UploadFiles(opForm, basePath, fileheaders, paths)
return
}
folderName := filepath.Dir(paths[0])
mcli, err := m.MachineFileApp.GetMachineCli(fid, basePath+"/"+folderName)
mcli, err := m.MachineFileApp.GetMachineCli(authCertName)
biz.ErrIsNil(err)
mi := mcli.Info
@@ -344,30 +345,30 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
}
func (m *MachineFile) RemoveFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.MachineFileOpForm))
opForm := req.BindJsonAndValid(rc, new(form.RemoveFileForm))
mi, err := m.MachineFileApp.RemoveFile(opForm)
mi, err := m.MachineFileApp.RemoveFile(opForm.MachineFileOpParam, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "path", opForm)
biz.ErrIsNilAppendErr(err, "删除文件失败: %s")
}
func (m *MachineFile) CopyFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.MachineFileOpForm))
mi, err := m.MachineFileApp.Copy(opForm)
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
mi, err := m.MachineFileApp.Copy(opForm.MachineFileOpParam, opForm.ToPath, opForm.Paths...)
biz.ErrIsNilAppendErr(err, "文件拷贝失败: %s")
rc.ReqParam = collx.Kvs("machine", mi, "cp", opForm)
}
func (m *MachineFile) MvFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.MachineFileOpForm))
mi, err := m.MachineFileApp.Mv(opForm)
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
mi, err := m.MachineFileApp.Mv(opForm.MachineFileOpParam, opForm.ToPath, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "mv", opForm)
biz.ErrIsNilAppendErr(err, "文件移动失败: %s")
}
func (m *MachineFile) Rename(rc *req.Ctx) {
renameForm := req.BindJsonAndValid(rc, new(form.MachineFileRename))
mi, err := m.MachineFileApp.Rename(renameForm)
renameForm := req.BindJsonAndValid(rc, new(form.RenameForm))
mi, err := m.MachineFileApp.Rename(renameForm.MachineFileOpParam, renameForm.Newname)
rc.ReqParam = collx.Kvs("machine", mi, "rename", renameForm)
biz.ErrIsNilAppendErr(err, "文件重命名失败: %s")
}

View File

@@ -13,7 +13,8 @@ type AuthCertBaseVO struct {
}
type MachineVO struct {
tagentity.ResourceTags
tagentity.ResourceTags // 标签信息
tagentity.AuthCerts // 授权凭证信息
Id uint64 `json:"id"`
Code string `json:"code"`
@@ -21,8 +22,6 @@ type MachineVO struct {
Protocol int `json:"protocol"`
Ip string `json:"ip"`
Port int `json:"port"`
Username string `json:"username"`
AuthCertId int `json:"authCertId"`
Status *int8 `json:"status"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
CreateTime *time.Time `json:"createTime"`
@@ -33,8 +32,6 @@ type MachineVO struct {
ModifierId *int64 `json:"modifierId"`
Remark *string `json:"remark"`
EnableRecorder int8 `json:"enableRecorder"`
// TagId uint64 `json:"tagId"`
// TagPath string `json:"tagPath"`
Stat map[string]any `json:"stat" gorm:"-"`
}