Files
oneterm/backend/internal/api/controller/asset.go
2025-07-16 18:11:04 +08:00

231 lines
7.0 KiB
Go

package controller
import (
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
"go.uber.org/zap"
"github.com/samber/lo"
"github.com/veops/oneterm/internal/model"
"github.com/veops/oneterm/internal/service"
"github.com/veops/oneterm/pkg/config"
"github.com/veops/oneterm/pkg/errors"
"github.com/veops/oneterm/pkg/logger"
)
const (
kFmtAssetIds = "assetIds-%d"
kAuthorizationIds = "authorizationIds"
kNodeIds = "nodeIds"
kAccountIds = "accountIds"
)
var (
assetService = service.NewAssetService()
assetPreHooks = []preHook[*model.Asset]{
// Preprocess asset data
func(ctx *gin.Context, data *model.Asset) {
assetService.PreprocessAssetData(data)
},
}
assetPostHooks = []postHook[*model.Asset]{
// Attach node chain
func(ctx *gin.Context, data []*model.Asset) {
if err := assetService.AttachNodeChain(ctx, data); err != nil {
logger.L().Error("attach node chain failed", zap.Error(err))
return
}
},
}
)
// 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...)
assetService.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, assetPreHooks...)
assetService.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) {
info := cast.ToBool(ctx.Query("info"))
// Build query with integrated V2 authorization filter
db, err := assetService.BuildQueryWithAuthorization(ctx)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &errors.ApiError{Code: errors.ErrInternal, Data: map[string]any{"err": err}})
return
}
// Apply parent_id filter if needed
if q, ok := ctx.GetQuery("parent_id"); ok {
db, err = assetService.FilterByParentId(db, cast.ToInt(q))
if err != nil {
logger.L().Error("parent id filtering failed", zap.Error(err))
ctx.AbortWithError(http.StatusInternalServerError, &errors.ApiError{Code: errors.ErrInternal, Data: map[string]any{"err": err}})
return
}
}
// Apply info mode settings
if info {
db = db.Select("id", "parent_id", "name", "ip", "protocols", "connectable", "authorization", "resource_id", "access_time_control", "asset_command_control")
}
doGet(ctx, false, db, config.RESOURCE_ASSET, assetPostHooks...)
}
// GetAssetIdsByAuthorization gets asset IDs by authorization
func GetAssetIdsByAuthorization(ctx *gin.Context) ([]int, error) {
// Use V2 authorization system for asset filtering
authV2Service := service.NewAuthorizationV2Service()
_, assetIds, _, err := authV2Service.GetAuthorizationScopeByACL(ctx)
return assetIds, err
}
// AssetPermissionResult represents simplified permission result without permissions field
type AssetPermissionResult struct {
Allowed bool `json:"allowed"`
Reason string `json:"reason"`
RuleId int `json:"rule_id"`
RuleName string `json:"rule_name"`
Restrictions map[string]interface{} `json:"restrictions"`
}
// AssetPermissionBatchResult represents batch permission results without permissions field
type AssetPermissionBatchResult struct {
Results map[model.AuthAction]*AssetPermissionResult `json:"results"`
}
// AssetPermissionMultiAccountResult represents permission results for multiple accounts
type AssetPermissionMultiAccountResult struct {
Results map[int]*AssetPermissionBatchResult `json:"results"` // accountId -> batch results
}
// GetAssetPermissions godoc
//
// @Tags asset
// @Param id path int true "asset id"
// @Param account_ids query string false "account ids (comma separated, e.g. 123,456,789)"
// @Success 200 {object} HttpResponse{data=AssetPermissionMultiAccountResult}
// @Router /asset/:id/permissions [get]
func (c *Controller) GetAssetPermissions(ctx *gin.Context) {
assetId := cast.ToInt(ctx.Param("id"))
accountIdsStr := ctx.Query("account_ids")
if assetId <= 0 {
ctx.AbortWithError(http.StatusBadRequest, &errors.ApiError{Code: errors.ErrInvalidArgument, Data: map[string]any{"err": "invalid asset id"}})
return
}
// Parse account_ids parameter
var accountIds []int
if accountIdsStr != "" {
// Split by comma and convert to integers
idStrs := strings.Split(accountIdsStr, ",")
for _, idStr := range idStrs {
idStr = strings.TrimSpace(idStr)
if idStr != "" {
if id, err := strconv.Atoi(idStr); err == nil && id > 0 {
accountIds = append(accountIds, id)
}
}
}
}
// Remove duplicates
accountIds = lo.Uniq(accountIds)
// Use V2 authorization service to get permissions
authV2Service := service.NewAuthorizationV2Service()
// If no account IDs provided, return empty result
if len(accountIds) == 0 {
ctx.JSON(http.StatusOK, HttpResponse{
Data: &AssetPermissionBatchResult{
Results: make(map[model.AuthAction]*AssetPermissionResult),
},
})
return
}
multiResult := &AssetPermissionMultiAccountResult{
Results: make(map[int]*AssetPermissionBatchResult),
}
for _, accId := range accountIds {
result, err := authV2Service.GetAssetPermissions(ctx, assetId, accId)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &errors.ApiError{Code: errors.ErrInternal, Data: map[string]any{"err": err}})
return
}
// Convert to simplified result without permissions field
simplifiedResult := &AssetPermissionBatchResult{
Results: make(map[model.AuthAction]*AssetPermissionResult),
}
for action, authResult := range result.Results {
simplifiedResult.Results[action] = &AssetPermissionResult{
Allowed: authResult.Allowed,
Reason: authResult.Reason,
RuleId: authResult.RuleId,
RuleName: authResult.RuleName,
Restrictions: authResult.Restrictions,
}
}
multiResult.Results[accId] = simplifiedResult
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: multiResult,
})
}