Files
oneterm/backend/internal/api/controller/asset.go

271 lines
7.5 KiB
Go

package controller
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"github.com/spf13/cast"
"go.uber.org/zap"
"github.com/veops/oneterm/internal/acl"
"github.com/veops/oneterm/internal/model"
"github.com/veops/oneterm/internal/repository"
"github.com/veops/oneterm/internal/schedule"
"github.com/veops/oneterm/pkg/config"
dbpkg "github.com/veops/oneterm/pkg/db"
"github.com/veops/oneterm/pkg/logger"
)
const (
kFmtAssetIds = "assetIds-%d"
kAuthorizationIds = "authorizationIds"
kNodeIds = "nodeIds"
kAccountIds = "accountIds"
)
var (
assetPreHooks = []preHook[*model.Asset]{
func(ctx *gin.Context, data *model.Asset) {
data.Ip = strings.TrimSpace(data.Ip)
data.Protocols = lo.Map(data.Protocols, func(s string, _ int) string { return strings.TrimSpace(s) })
if data.Authorization == nil {
data.Authorization = make(model.Map[int, model.Slice[int]])
}
},
}
assetPostHooks = []postHook[*model.Asset]{assetPostHookCount, assetPostHookAuth}
)
// CreateAsset godoc
//
// @Tags asset
// @Param asset body model.Asset true "asset"
// @Success 200 {object} HttpResponse
// @Router /asset [post]
func (c *Controller) CreateAsset(ctx *gin.Context) {
asset := &model.Asset{}
doCreate(ctx, true, asset, config.RESOURCE_ASSET, assetPreHooks...)
schedule.UpdateConnectables(asset.Id)
}
// DeleteAsset godoc
//
// @Tags asset
// @Param id path int true "asset id"
// @Success 200 {object} HttpResponse
// @Router /asset/:id [delete]
func (c *Controller) DeleteAsset(ctx *gin.Context) {
doDelete(ctx, true, &model.Asset{}, config.RESOURCE_ASSET)
}
// UpdateAsset godoc
//
// @Tags asset
// @Param id path int true "asset id"
// @Param asset body model.Asset true "asset"
// @Success 200 {object} HttpResponse
// @Router /asset/:id [put]
func (c *Controller) UpdateAsset(ctx *gin.Context) {
doUpdate(ctx, true, &model.Asset{}, config.RESOURCE_ASSET)
schedule.UpdateConnectables(cast.ToInt(ctx.Param("id")))
}
// GetAssets godoc
//
// @Tags asset
// @Param page_index query int true "page_index"
// @Param page_size query int true "page_size"
// @Param search query string false "name or ip"
// @Param id query int false "asset id"
// @Param ids query string false "asset ids"
// @Param parent_id query int false "asset's parent id"
// @Param name query string false "asset name"
// @Param ip query string false "asset ip"
// @Param info query bool false "is info mode"
// @Success 200 {object} HttpResponse{data=ListData{list=[]model.Asset}}
// @Router /asset [get]
func (c *Controller) GetAssets(ctx *gin.Context) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
info := cast.ToBool(ctx.Query("info"))
db := dbpkg.DB.Model(model.DefaultAsset)
db = filterEqual(ctx, db, "id")
db = filterLike(ctx, db, "name", "ip")
db = filterSearch(ctx, db, "name", "ip")
if q, ok := ctx.GetQuery("ids"); ok {
db = db.Where("id IN ?", lo.Map(strings.Split(q, ","), func(s string, _ int) int { return cast.ToInt(s) }))
}
if q, ok := ctx.GetQuery("parent_id"); ok {
parentIds, err := handleParentId(ctx, cast.ToInt(q))
if err != nil {
logger.L().Error("parent id found failed", zap.Error(err))
return
}
db = db.Where("parent_id IN ?", parentIds)
}
if info {
db = db.Select("id", "parent_id", "name", "ip", "protocols", "connectable", "authorization")
if !acl.IsAdmin(currentUser) {
ids, err := GetAssetIdsByAuthorization(ctx)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
return
}
db = db.Where("id IN ?", ids)
}
}
db = db.Order("name")
doGet(ctx, !info, db, config.RESOURCE_ASSET, assetPostHooks...)
}
func assetPostHookCount(ctx *gin.Context, data []*model.Asset) {
nodes, err := repository.GetAllFromCacheDb(ctx, model.DefaultNode)
if err != nil {
return
}
g := make(map[int][]model.Pair[int, string])
for _, n := range nodes {
g[n.ParentId] = append(g[n.ParentId], model.Pair[int, string]{First: n.Id, Second: n.Name})
}
m := make(map[int]string)
var dfs func(int, string)
dfs = func(x int, s string) {
m[x] = s
for _, node := range g[x] {
dfs(node.First, fmt.Sprintf("%s/%s", s, node.Second))
}
}
dfs(0, "")
for _, d := range data {
d.NodeChain = m[d.ParentId]
}
}
func assetPostHookAuth(ctx *gin.Context, data []*model.Asset) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
if acl.IsAdmin(currentUser) {
return
}
info := cast.ToBool(ctx.Query("info"))
noInfoIds := make([]int, 0)
if !info {
t := dbpkg.DB.Model(model.DefaultAsset)
assetResIds, _ := acl.GetRoleResourceIds(ctx, currentUser.GetRid(), config.RESOURCE_ASSET)
t, _ = handleAssetIds(ctx, t, assetResIds)
t.Pluck("id", &noInfoIds)
}
authorizationIds, _ := ctx.Value(kAuthorizationIds).([]*model.AuthorizationIds)
nodeIds, _, accountIds := getIdsByAuthorizationIds(ctx)
nodeIds, _ = handleSelfChild(ctx, nodeIds...)
for _, a := range data {
if lo.Contains(nodeIds, a.ParentId) || lo.Contains(noInfoIds, a.Id) {
continue
}
if lo.ContainsBy(authorizationIds, func(item *model.AuthorizationIds) bool {
return item.AssetId == a.Id && item.NodeId == 0 && item.AccountId == 0
}) {
continue
}
ids := lo.Map(lo.Filter(authorizationIds, func(item *model.AuthorizationIds, _ int) bool {
return item.AssetId == a.Id && item.AccountId != 0 && item.NodeId == 0
}),
func(item *model.AuthorizationIds, _ int) int { return item.AccountId })
for k := range a.Authorization {
if !lo.Contains(ids, k) && !lo.Contains(accountIds, k) {
delete(a.Authorization, k)
}
}
}
}
func handleParentId(ctx context.Context, parentId int) (pids []int, err error) {
nodes, err := repository.GetAllFromCacheDb(ctx, model.DefaultNode)
if err != nil {
return
}
g := make(map[int][]int)
for _, n := range nodes {
g[n.ParentId] = append(g[n.ParentId], n.Id)
}
var dfs func(int)
dfs = func(x int) {
pids = append(pids, x)
for _, y := range g[x] {
dfs(y)
}
}
dfs(parentId)
return
}
func GetAssetIdsByAuthorization(ctx *gin.Context) (ids []int, err error) {
authIds, err := getAuthorizationIds(ctx)
if err != nil {
return
}
ctx.Set(kAuthorizationIds, authIds)
nodeIds, ids, accountIds := getIdsByAuthorizationIds(ctx)
tmp, err := handleSelfChild(ctx, nodeIds...)
if err != nil {
return
}
nodeIds = append(nodeIds, tmp...)
ctx.Set(kNodeIds, nodeIds)
ctx.Set(kAccountIds, accountIds)
tmp, err = getAssetIdsByNodeAccount(ctx, nodeIds, accountIds)
if err != nil {
return
}
ids = lo.Uniq(append(ids, tmp...))
return
}
func getIdsByAuthorizationIds(ctx *gin.Context) (nodeIds, assetIds, accountIds []int) {
authIds, _ := ctx.Value(kAuthorizationIds).([]*model.AuthorizationIds)
info := cast.ToBool(ctx.Query("info"))
for _, a := range authIds {
if a.NodeId != 0 && a.AssetId == 0 && a.AccountId == 0 {
nodeIds = append(nodeIds, a.NodeId)
}
if a.AssetId != 0 && a.NodeId == 0 && (info || a.AccountId == 0) {
assetIds = append(assetIds, a.AssetId)
}
if a.AccountId != 0 && a.AssetId == 0 && (info || a.NodeId == 0) {
accountIds = append(accountIds, a.AccountId)
}
}
return
}
func getAssetIdsByNodeAccount(ctx context.Context, nodeIds, accountIds []int) (assetIds []int, err error) {
assets, err := repository.GetAllFromCacheDb(ctx, model.DefaultAsset)
if err != nil {
return
}
assets = lo.Filter(assets, func(a *model.Asset, _ int) bool {
return lo.Contains(nodeIds, a.ParentId) || len(lo.Intersect(lo.Keys(a.Authorization), accountIds)) > 0
})
assetIds = lo.Map(assets, func(a *model.Asset, _ int) int { return a.Id })
return
}