mirror of
https://github.com/veops/oneterm.git
synced 2025-10-05 23:37:03 +08:00
362 lines
10 KiB
Go
362 lines
10 KiB
Go
package acl
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/samber/lo"
|
|
"github.com/spf13/cast"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"github.com/veops/oneterm/internal/model"
|
|
"github.com/veops/oneterm/pkg/config"
|
|
dbpkg "github.com/veops/oneterm/pkg/db"
|
|
"github.com/veops/oneterm/pkg/logger"
|
|
"github.com/veops/oneterm/pkg/remote"
|
|
)
|
|
|
|
type ResourceType struct {
|
|
Name string `json:"name"`
|
|
Perms []string `json:"perms"`
|
|
}
|
|
|
|
type ResourceTypeResp struct {
|
|
Groups []*ResourceType `json:"groups"`
|
|
}
|
|
|
|
func MigrateNode() {
|
|
ctx := context.Background()
|
|
|
|
rts, err := GetResourceTypes(ctx)
|
|
if err != nil {
|
|
logger.L().Fatal("get resource type failed", zap.Error(err))
|
|
}
|
|
|
|
if !lo.ContainsBy(rts, func(rt *ResourceType) bool { return rt.Name == "node" }) {
|
|
if err = AddResourceTypes(ctx, &ResourceType{Name: "node", Perms: AllPermissions}); err != nil {
|
|
logger.L().Fatal("add resource type failed", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
nodes := make([]*model.Node, 0)
|
|
if err = dbpkg.DB.Model(&nodes).Where("resource_id = 0").Or("resource_id IS NULL").Find(&nodes).Error; err != nil {
|
|
logger.L().Fatal("get nodes failed", zap.Error(err))
|
|
}
|
|
eg := errgroup.Group{}
|
|
for _, n := range nodes {
|
|
nd := n
|
|
eg.Go(func() error {
|
|
r, err := AddResource(ctx, nd.CreatorId, "node", cast.ToString(nd.Id))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := dbpkg.DB.Model(&nd).Where("id=?", nd.Id).Update("resource_id", r.ResourceId).Error; err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
if err = eg.Wait(); err != nil {
|
|
logger.L().Fatal("add resource failed", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func MigrateCommand() {
|
|
ctx := context.Background()
|
|
|
|
rts, err := GetResourceTypes(ctx)
|
|
if err != nil {
|
|
logger.L().Fatal("get resource type failed", zap.Error(err))
|
|
}
|
|
|
|
// Ensure command resource type exists
|
|
if !lo.ContainsBy(rts, func(rt *ResourceType) bool { return rt.Name == "command" }) {
|
|
if err = AddResourceTypes(ctx, &ResourceType{Name: "command", Perms: AllPermissions}); err != nil {
|
|
logger.L().Fatal("add command resource type failed", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
// Ensure command_template resource type exists
|
|
if !lo.ContainsBy(rts, func(rt *ResourceType) bool { return rt.Name == "command_template" }) {
|
|
if err = AddResourceTypes(ctx, &ResourceType{Name: "command_template", Perms: AllPermissions}); err != nil {
|
|
logger.L().Fatal("add command_template resource type failed", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
// Migrate Commands
|
|
commands := make([]*model.Command, 0)
|
|
if err = dbpkg.DB.Model(&commands).Where("resource_id = 0").Or("resource_id IS NULL").Find(&commands).Error; err != nil {
|
|
logger.L().Fatal("get commands failed", zap.Error(err))
|
|
}
|
|
|
|
// Get existing command resources to avoid duplicates
|
|
existingCommandResources, err := GetCommandResources(ctx)
|
|
if err != nil {
|
|
logger.L().Fatal("get existing command resources failed", zap.Error(err))
|
|
}
|
|
commandNameToResourceId := lo.SliceToMap(existingCommandResources, func(r *Resource) (string, int) {
|
|
return r.Name, r.ResourceId
|
|
})
|
|
|
|
eg := errgroup.Group{}
|
|
for _, c := range commands {
|
|
cmd := c
|
|
eg.Go(func() error {
|
|
var resourceId int
|
|
|
|
// Check if resource already exists
|
|
if existingResourceId, exists := commandNameToResourceId[cmd.Name]; exists {
|
|
resourceId = existingResourceId
|
|
logger.L().Info("Using existing resource for command",
|
|
zap.String("command", cmd.Name),
|
|
zap.Int("resource_id", resourceId))
|
|
} else {
|
|
// Create new resource
|
|
r, err := AddResource(ctx, cmd.CreatorId, "command", cmd.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resourceId = r.ResourceId
|
|
logger.L().Info("Created new resource for command",
|
|
zap.String("command", cmd.Name),
|
|
zap.Int("resource_id", resourceId))
|
|
}
|
|
|
|
// Update command with resource_id
|
|
if err := dbpkg.DB.Model(&cmd).Where("id=?", cmd.Id).Update("resource_id", resourceId).Error; err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Migrate CommandTemplates
|
|
templates := make([]*model.CommandTemplate, 0)
|
|
if err = dbpkg.DB.Model(&templates).Where("resource_id = 0").Or("resource_id IS NULL").Find(&templates).Error; err != nil {
|
|
logger.L().Fatal("get command templates failed", zap.Error(err))
|
|
}
|
|
|
|
// Get existing command_template resources to avoid duplicates
|
|
existingTemplateResources, err := GetCommandTemplateResources(ctx)
|
|
if err != nil {
|
|
logger.L().Fatal("get existing command template resources failed", zap.Error(err))
|
|
}
|
|
templateNameToResourceId := lo.SliceToMap(existingTemplateResources, func(r *Resource) (string, int) {
|
|
return r.Name, r.ResourceId
|
|
})
|
|
|
|
for _, t := range templates {
|
|
tmpl := t
|
|
eg.Go(func() error {
|
|
var resourceId int
|
|
|
|
// Check if resource already exists
|
|
if existingResourceId, exists := templateNameToResourceId[tmpl.Name]; exists {
|
|
resourceId = existingResourceId
|
|
logger.L().Info("Using existing resource for command template",
|
|
zap.String("template", tmpl.Name),
|
|
zap.Int("resource_id", resourceId))
|
|
} else {
|
|
// Create new resource
|
|
r, err := AddResource(ctx, tmpl.CreatorId, "command_template", tmpl.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resourceId = r.ResourceId
|
|
logger.L().Info("Created new resource for command template",
|
|
zap.String("template", tmpl.Name),
|
|
zap.Int("resource_id", resourceId))
|
|
}
|
|
|
|
// Update template with resource_id
|
|
if err := dbpkg.DB.Model(&tmpl).Where("id=?", tmpl.Id).Update("resource_id", resourceId).Error; err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
if err = eg.Wait(); err != nil {
|
|
logger.L().Fatal("migrate command and template resources failed", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
// GetCommandResources retrieves all command resources from ACL system
|
|
func GetCommandResources(ctx context.Context) ([]*Resource, error) {
|
|
token, err := remote.GetAclToken(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data := &ResourceResult{}
|
|
url := fmt.Sprintf("%s/acl/resources", config.Cfg.Auth.Acl.Url)
|
|
resp, err := remote.RC.R().
|
|
SetHeader("App-Access-Token", token).
|
|
SetQueryParams(map[string]string{
|
|
"app_id": config.Cfg.Auth.Acl.AppId,
|
|
"resource_type_id": "command",
|
|
"page_size": "1000", // Get all resources
|
|
}).
|
|
SetResult(data).
|
|
Get(url)
|
|
|
|
if err = remote.HandleErr(err, resp, func(dt map[string]any) bool { return true }); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return data.Resources, nil
|
|
}
|
|
|
|
// GetCommandTemplateResources retrieves all command template resources from ACL system
|
|
func GetCommandTemplateResources(ctx context.Context) ([]*Resource, error) {
|
|
token, err := remote.GetAclToken(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data := &ResourceResult{}
|
|
url := fmt.Sprintf("%s/acl/resources", config.Cfg.Auth.Acl.Url)
|
|
resp, err := remote.RC.R().
|
|
SetHeader("App-Access-Token", token).
|
|
SetQueryParams(map[string]string{
|
|
"app_id": config.Cfg.Auth.Acl.AppId,
|
|
"resource_type_id": "command_template",
|
|
"page_size": "1000", // Get all resources
|
|
}).
|
|
SetResult(data).
|
|
Get(url)
|
|
|
|
if err = remote.HandleErr(err, resp, func(dt map[string]any) bool { return true }); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return data.Resources, nil
|
|
}
|
|
|
|
func GetResourceTypes(ctx context.Context) (rt []*ResourceType, err error) {
|
|
token, err := remote.GetAclToken(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
data := &ResourceTypeResp{}
|
|
url := fmt.Sprintf("%s/acl/resource_types", config.Cfg.Auth.Acl.Url)
|
|
resp, err := remote.RC.R().
|
|
SetHeaders(map[string]string{
|
|
"App-Access-Token": token,
|
|
}).
|
|
SetQueryParams(map[string]string{
|
|
"app_id": "oneterm",
|
|
"page_size": "100",
|
|
}).
|
|
SetResult(data).
|
|
Get(url)
|
|
err = remote.HandleErr(err, resp, func(dt map[string]any) bool { return true })
|
|
|
|
rt = data.Groups
|
|
|
|
return
|
|
}
|
|
|
|
func AddResourceTypes(ctx context.Context, rt *ResourceType) (err error) {
|
|
token, err := remote.GetAclToken(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/acl/resource_types", config.Cfg.Auth.Acl.Url)
|
|
resp, err := remote.RC.R().
|
|
SetHeaders(map[string]string{
|
|
"App-Access-Token": token,
|
|
}).
|
|
SetBody(rt).
|
|
Post(url)
|
|
err = remote.HandleErr(err, resp, func(dt map[string]any) bool { return true })
|
|
|
|
return
|
|
}
|
|
|
|
func AddResource(ctx context.Context, uid int, resourceTypeId string, name string) (res *Resource, err error) {
|
|
token, err := remote.GetAclToken(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
res = &Resource{}
|
|
url := fmt.Sprintf("%s/acl/resources", config.Cfg.Auth.Acl.Url)
|
|
resp, err := remote.RC.R().
|
|
SetHeaders(map[string]string{
|
|
"App-Access-Token": token,
|
|
"X-User-Id": cast.ToString(uid),
|
|
"Accept-Language": getAcceptLanguage(ctx)}).
|
|
SetBody(map[string]any{
|
|
"type_id": resourceTypeId,
|
|
"name": name,
|
|
"uid": uid,
|
|
}).
|
|
SetResult(&res).
|
|
Post(url)
|
|
err = remote.HandleErr(err, resp, func(dt map[string]any) bool { return true })
|
|
return
|
|
}
|
|
|
|
func DeleteResource(ctx context.Context, uid int, resourceId int) (err error) {
|
|
token, err := remote.GetAclToken(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
url := fmt.Sprintf("%v/acl/resources/%v", config.Cfg.Auth.Acl.Url, resourceId)
|
|
resp, err := remote.RC.R().
|
|
SetHeaders(map[string]string{
|
|
"App-Access-Token": token,
|
|
"X-User-Id": cast.ToString(uid),
|
|
"Accept-Language": getAcceptLanguage(ctx)}).
|
|
Delete(url)
|
|
err = remote.HandleErr(err, resp, func(dt map[string]any) bool { return true })
|
|
return
|
|
}
|
|
|
|
func UpdateResource(ctx context.Context, uid int, resourceId int, updates map[string]string) (err error) {
|
|
token, err := remote.GetAclToken(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/acl/resources/%d", config.Cfg.Auth.Acl.Url, resourceId)
|
|
resp, err := remote.RC.R().
|
|
SetHeaders(map[string]string{
|
|
"App-Access-Token": token,
|
|
"X-User-Id": cast.ToString(uid),
|
|
"Accept-Language": getAcceptLanguage(ctx)}).
|
|
SetFormData(updates).
|
|
Put(url)
|
|
err = remote.HandleErr(err, resp, func(dt map[string]any) bool { return true })
|
|
return
|
|
}
|
|
|
|
func GetResourcePermissions(ctx context.Context, resourceId int) (res map[string]*ResourcePermissionsRespItem, err error) {
|
|
token, err := remote.GetAclToken(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
res = make(map[string]*ResourcePermissionsRespItem)
|
|
url := fmt.Sprintf("%v/acl/resources/%v/permissions", config.Cfg.Auth.Acl.Url, resourceId) //TODO config
|
|
resp, err := remote.RC.R().
|
|
SetHeader("App-Access-Token", token).
|
|
SetResult(&res).
|
|
Get(url)
|
|
err = remote.HandleErr(err, resp, func(dt map[string]any) bool { return true })
|
|
return
|
|
}
|
|
|
|
func getAcceptLanguage(ctx context.Context) string {
|
|
ginCtx, ok := ctx.(*gin.Context)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return ginCtx.GetHeader("Accept-Language")
|
|
}
|