feat(api): permission

This commit is contained in:
ttk
2024-09-26 12:55:44 +08:00
parent 7ed7646954
commit c646a98059
20 changed files with 271 additions and 189 deletions

View File

@@ -4,8 +4,6 @@ package acl
import (
"context"
"fmt"
"github.com/veops/oneterm/conf"
)
func GetSessionFromCtx(ctx context.Context) (res *Session, err error) {
@@ -25,21 +23,8 @@ func IsAdmin(session *Session) bool {
return false
}
func GetResourceTypeName(resourceType string) string {
names := conf.Cfg.Auth.Acl.ResourceNames
for _, v := range names {
if v.Key == resourceType {
return v.Value
}
}
return "NONE"
}
func CreateGrantAcl(ctx context.Context, session *Session, resourceType string, resourceName string) (resourceId int, err error) {
resource, err := AddResource(ctx,
session.GetUid(),
GetResourceTypeName(resourceType),
resourceName)
resource, err := AddResource(ctx, session.GetUid(), resourceType, resourceName)
if err != nil {
return
}
@@ -54,10 +39,7 @@ func CreateGrantAcl(ctx context.Context, session *Session, resourceType string,
}
func CreateAcl(ctx context.Context, session *Session, resourceType string, resourceName string) (resourceId int, err error) {
resource, err := AddResource(ctx,
session.GetUid(),
GetResourceTypeName(resourceType),
resourceName)
resource, err := AddResource(ctx, session.GetUid(), resourceType, resourceName)
if err != nil {
return
}

View File

@@ -23,6 +23,7 @@ func init() {
type ResourceType struct {
Name string `json:"name"`
Perms []string `json:"perms"`
}
type ResourceTypeResp struct {
@@ -37,8 +38,8 @@ func migrateNode() {
logger.L().Fatal("get resource type failed", zap.Error(err))
}
if _, ok := lo.Find(rts, func(rt *ResourceType) bool { return rt.Name == "node" }); !ok {
if err = AddResourceTypes(ctx, &ResourceType{Name: "node"}); err != nil {
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))
}
}
@@ -51,7 +52,7 @@ func migrateNode() {
for _, n := range nodes {
nd := n
eg.Go(func() error {
r, err := AddResource(ctx, 0, "node", cast.ToString(nd.Id))
r, err := AddResource(ctx, nd.CreatorId, "node", cast.ToString(nd.Id))
if err != nil {
return err
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"time"
"github.com/samber/lo"
"github.com/spf13/cast"
"golang.org/x/sync/errgroup"
@@ -50,6 +51,16 @@ func GetRoleResources(ctx context.Context, rid int, resourceTypeId string) (res
return
}
func GetRoleResourceIds(ctx context.Context, rid int, resourceTypeId string) (ids []int, err error) {
res, err := GetRoleResources(ctx, rid, resourceTypeId)
if err != nil {
return
}
ids = lo.Map(res, func(r *Resource, _ int) int { return r.ResourceId })
return
}
func HasPermission(ctx context.Context, rid int, resourceTypeName string, resourceId int, permission string) (res bool, err error) {
token, err := remote.GetAclToken(ctx)
if err != nil {
@@ -75,6 +86,7 @@ func HasPermission(ctx context.Context, rid int, resourceTypeName string, resour
if v, ok := data["result"]; ok {
res = v.(bool)
}
return
}

View File

@@ -162,7 +162,7 @@ func (c *Controller) GetAccounts(ctx *gin.Context) {
db = db.Order("name")
doGet(ctx, !info, db, acl.GetResourceTypeName(conf.RESOURCE_ACCOUNT), accountPostHooks...)
doGet(ctx, !info, db, conf.RESOURCE_ACCOUNT, accountPostHooks...)
}
func GetAccountIdsByAuthorization(ctx *gin.Context) (ids []int, err error) {
@@ -178,12 +178,12 @@ func GetAccountIdsByAuthorization(ctx *gin.Context) (ids []int, err error) {
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
return
}
ss := make([][]int, 0)
ss := make([]model.Slice[string], 0)
if err = mysql.DB.Model(&model.Asset{}).Where("id IN ?", assetIds).Pluck("JSON_KEYS(authorization)", &ss).Error; err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
return
}
ids = lo.Uniq(lo.Flatten(ss))
ids = lo.Uniq(lo.Map(lo.Flatten(ss), func(s string, _ int) int { return cast.ToInt(s) }))
_, _, accountIds := getIdsByAuthorizationIds(ctx)
ids = lo.Uniq(append(ids, accountIds...))

View File

@@ -24,7 +24,7 @@ import (
const (
kFmtAssetIds = "assetIds-%d"
kAuthorizationIds = "authorizationIds"
kParentNodeIds = "parentNodeIds"
kNodeIds = "nodeIds"
kAccountIds = "accountIds"
)
@@ -121,7 +121,7 @@ func (c *Controller) GetAssets(ctx *gin.Context) {
db = db.Order("name")
doGet(ctx, !info, db, acl.GetResourceTypeName(conf.RESOURCE_ASSET), assetPostHooks...)
doGet(ctx, !info, db, conf.RESOURCE_ASSET, assetPostHooks...)
}
func assetPostHookCount(ctx *gin.Context, data []*model.Asset) {
@@ -154,17 +154,23 @@ func assetPostHookAuth(ctx *gin.Context, data []*model.Asset) {
if acl.IsAdmin(currentUser) {
return
}
authorizationIds, _ := ctx.Value("authorizationIds").([]*model.AuthorizationIds)
parentNodeIds, _, accountIds := getIdsByAuthorizationIds(ctx)
authorizationIds, _ := ctx.Value(kAuthorizationIds).([]*model.AuthorizationIds)
nodeIds, _, accountIds := getIdsByAuthorizationIds(ctx)
nodeIds, _ = handleSelfChild(ctx, nodeIds...)
for _, a := range data {
if lo.Contains(parentNodeIds, a.Id) {
if lo.Contains(nodeIds, a.ParentId) {
continue
}
ids := lo.Uniq(
lo.Map(lo.Filter(authorizationIds, func(item *model.AuthorizationIds, _ int) bool {
return item.AssetId != nil && *item.AssetId == a.Id && item.AccountId != nil
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 }))
func(item *model.AuthorizationIds, _ int) int { return item.AccountId })
for k := range a.Authorization {
if !lo.Contains(ids, k) && !lo.Contains(accountIds, k) {
@@ -210,16 +216,16 @@ func GetAssetIdsByAuthorization(ctx *gin.Context) (ids []int, err error) {
return
}
parentNodeIds, ids, accountIds := getIdsByAuthorizationIds(ctx)
nodeIds, ids, accountIds := getIdsByAuthorizationIds(ctx)
tmp, err := handleSelfChild(ctx, parentNodeIds)
tmp, err := handleSelfChild(ctx, nodeIds...)
if err != nil {
return
}
parentNodeIds = append(parentNodeIds, tmp...)
ctx.Set(kParentNodeIds, parentNodeIds)
nodeIds = append(nodeIds, tmp...)
ctx.Set(kNodeIds, nodeIds)
ctx.Set(kAccountIds, accountIds)
tmp, err = getAssetIdsByNodeAccount(ctx, parentNodeIds, accountIds)
tmp, err = getAssetIdsByNodeAccount(ctx, nodeIds, accountIds)
if err != nil {
return
}
@@ -230,24 +236,23 @@ func GetAssetIdsByAuthorization(ctx *gin.Context) (ids []int, err error) {
return
}
func getIdsByAuthorizationIds(ctx context.Context) (parentNodeIds, assetIds, accountIds []int) {
func getIdsByAuthorizationIds(ctx context.Context) (nodeIds, assetIds, accountIds []int) {
authIds, _ := ctx.Value(kAuthorizationIds).([]*model.AuthorizationIds)
for _, a := range authIds {
if a.NodeId != nil && a.AssetId == nil && a.AccountId == nil {
parentNodeIds = append(parentNodeIds, *a.NodeId)
if a.NodeId != 0 && a.AssetId == 0 && a.AccountId == 0 {
nodeIds = append(nodeIds, a.NodeId)
}
if a.AssetId != nil && a.NodeId == nil && a.AccountId == nil {
assetIds = append(assetIds, *a.AssetId)
if a.AssetId != 0 && a.NodeId == 0 && a.AccountId == 0 {
assetIds = append(assetIds, a.AssetId)
}
if a.AccountId != nil && a.AssetId == nil && a.NodeId == nil {
accountIds = append(accountIds, *a.AccountId)
if a.AccountId != 0 && a.AssetId == 0 && a.NodeId == 0 {
accountIds = append(accountIds, a.AccountId)
}
}
return
}
func getAssetIdsByNodeAccount(ctx context.Context, parentNodeIds, accountIds []int) (assetIds []int, err error) {
err = mysql.DB.WithContext(ctx).Model(&model.Asset{}).Where("parent_id IN?", parentNodeIds).Or("JSON_KEYS(authorization) IN ?", accountIds).Pluck("id", &assetIds).Error
func getAssetIdsByNodeAccount(ctx context.Context, nodeIds, accountIds []int) (assetIds []int, err error) {
err = mysql.DB.WithContext(ctx).Model(&model.Asset{}).Where("parent_id IN?", nodeIds).Or("JSON_KEYS(authorization) IN ?", accountIds).Pluck("id", &assetIds).Error
return
}

View File

@@ -45,11 +45,7 @@ func (c *Controller) UpsertAuthorization(ctx *gin.Context) {
if err := mysql.DB.Transaction(func(tx *gorm.DB) error {
t := &model.Authorization{}
if err = tx.Model(t).
Where(fmt.Sprintf("node_id %s AND asset_id %s AND account_id %s",
lo.Ternary(auth.NodeId == nil, "IS NULL", fmt.Sprintf("=%d", cast.ToInt(auth.NodeId))),
lo.Ternary(auth.AssetId == nil, "IS NULL", fmt.Sprintf("=%d", cast.ToInt(auth.AssetId))),
lo.Ternary(auth.AccountId == nil, "IS NULL", fmt.Sprintf("=%d", cast.ToInt(auth.AccountId))),
)).
Where("node_id=? AND asset_id=? AND account_id=?", auth.NodeId, auth.AssetId, auth.AccountId).
First(t).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
@@ -132,14 +128,14 @@ func (c *Controller) GetAuthorizations(ctx *gin.Context) {
db = db.Where(fmt.Sprintf("%s=?", k), cast.ToInt(q))
switch k {
case "node_id":
auth.NodeId = lo.ToPtr(cast.ToInt(q))
auth.NodeId = cast.ToInt(q)
case "asset_id":
auth.AssetId = lo.ToPtr(cast.ToInt(q))
auth.AssetId = cast.ToInt(q)
case "account_id":
auth.AccountId = lo.ToPtr(cast.ToInt(q))
auth.AccountId = cast.ToInt(q)
}
} else {
db = db.Where(fmt.Sprintf("%s IS NULL", k))
db = db.Where("?=0", k)
}
}
@@ -148,7 +144,7 @@ func (c *Controller) GetAuthorizations(ctx *gin.Context) {
return
}
doGet[*model.Authorization](ctx, false, db, acl.GetResourceTypeName(conf.RESOURCE_AUTHORIZATION))
doGet[*model.Authorization](ctx, false, db, conf.RESOURCE_AUTHORIZATION)
}
func getGrantNodeAssetAccoutIds(ctx context.Context, action string) (nodeIds, assetIds, accountIds []int, err error) {
@@ -164,14 +160,14 @@ func getGrantNodeAssetAccoutIds(ctx context.Context, action string) (nodeIds, as
return
}
res = lo.Filter(res, func(r *acl.Resource, _ int) bool { return lo.Contains(r.Permissions, action) })
resIds, err := handleSelfChild(ctx, lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId }))
resIds, err := handleSelfChild(ctx, lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId })...)
if err != nil {
return
}
if err = mysql.DB.Model(&model.Node{}).Where("resource_id IN ?", resIds).Pluck("id", &nodeIds).Error; err != nil {
return
}
nodeIds, err = handleSelfChild(ctx, nodeIds)
nodeIds, err = handleSelfChild(ctx, nodeIds...)
return
})
@@ -181,7 +177,7 @@ func getGrantNodeAssetAccoutIds(ctx context.Context, action string) (nodeIds, as
return
}
res = lo.Filter(res, func(r *acl.Resource, _ int) bool { return lo.Contains(r.Permissions, action) })
resIds, err := handleSelfChild(ctx, lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId }))
resIds, err := handleSelfChild(ctx, lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId })...)
if err != nil {
return
}
@@ -198,7 +194,7 @@ func getGrantNodeAssetAccoutIds(ctx context.Context, action string) (nodeIds, as
return
}
res = lo.Filter(res, func(r *acl.Resource, _ int) bool { return lo.Contains(r.Permissions, action) })
resIds, err := handleSelfChild(ctx, lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId }))
resIds, err := handleSelfChild(ctx, lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId })...)
if err != nil {
return
}
@@ -225,12 +221,12 @@ func hasPermAuthorization(ctx context.Context, auth *model.Authorization, action
return
}
if auth.NodeId != nil && auth.AssetId == nil && auth.AccountId == nil {
ok = lo.Contains(nodeIds, *auth.NodeId)
} else if auth.AssetId != nil && auth.NodeId == nil && auth.AccountId == nil {
ok = lo.Contains(assetIds, *auth.AssetId)
} else if auth.AccountId != nil && auth.AssetId == nil && auth.NodeId == nil {
ok = lo.Contains(accountIds, *auth.AccountId)
if auth.NodeId != 0 && auth.AssetId == 0 && auth.AccountId == 0 {
ok = lo.Contains(nodeIds, auth.NodeId)
} else if auth.AssetId != 0 && auth.NodeId == 0 && auth.AccountId == 0 {
ok = lo.Contains(assetIds, auth.AssetId)
} else if auth.AccountId != 0 && auth.AssetId == 0 && auth.NodeId == 0 {
ok = lo.Contains(accountIds, auth.AccountId)
}
return
@@ -260,7 +256,7 @@ func handleAuthorization(ctx *gin.Context, tx *gorm.DB, action int, asset *model
}
for _, pre := range pres {
p := pre
if _, ok := asset.Authorization[*p.AccountId]; ok {
if _, ok := asset.Authorization[p.AccountId]; ok {
auths = append(auths, p)
} else {
eg.Go(func() error {
@@ -270,7 +266,7 @@ func handleAuthorization(ctx *gin.Context, tx *gorm.DB, action int, asset *model
}
} else {
auths = lo.Map(lo.Keys(asset.Authorization), func(id int, _ int) *model.Authorization {
return &model.Authorization{AssetId: &asset.Id, AccountId: &id, Rids: asset.Authorization[id]}
return &model.Authorization{AssetId: asset.Id, AccountId: id, Rids: asset.Authorization[id]}
})
}
}
@@ -281,7 +277,7 @@ func handleAuthorization(ctx *gin.Context, tx *gorm.DB, action int, asset *model
case model.ACTION_CREATE:
eg.Go(func() (err error) {
resourceId := 0
if resourceId, err = acl.CreateGrantAcl(ctx, currentUser, conf.GetResourceTypeName(conf.RESOURCE_AUTHORIZATION), auth.GetName()); err != nil {
if resourceId, err = acl.CreateGrantAcl(ctx, currentUser, conf.RESOURCE_AUTHORIZATION, auth.GetName()); err != nil {
return
}
if err = acl.BatchGrantRoleResource(ctx, currentUser.GetUid(), auth.Rids, resourceId, []string{acl.READ}); err != nil {
@@ -342,17 +338,6 @@ func getAuthorizations(ctx *gin.Context) (res []*acl.Resource, err error) {
return
}
func getAutorizationResourceIdPerms(ctx *gin.Context) (resourceIdPerms map[int][]string, err error) {
res, err := getAuthorizations(ctx)
if err != nil {
return
}
resourceIdPerms = lo.SliceToMap(res, func(r *acl.Resource) (int, []string) { return r.ResourceId, r.Permissions })
return
}
func getAutorizationResourceIds(ctx *gin.Context) (resourceIds []int, err error) {
res, err := getAuthorizations(ctx)
if err != nil {
@@ -404,15 +389,15 @@ func hasAuthorization(ctx *gin.Context, sess *gsession.Session) (ok bool) {
if err != nil {
return
}
if _, ok = lo.Find(authIds, func(item *model.AuthorizationIds) bool {
return item.NodeId == nil && item.AssetId != nil && *item.AssetId == sess.AssetId && item.AccountId != nil && *item.AccountId == sess.AccountId
}); ok {
if lo.ContainsBy(authIds, func(item *model.AuthorizationIds) bool {
return item.NodeId == 0 && item.AssetId == sess.AssetId && item.AccountId == sess.AccountId
}) {
return
}
ctx.Set(kAuthorizationIds, authIds)
parentNodeIds, assetIds, accountIds := getIdsByAuthorizationIds(ctx)
tmp, err := handleSelfChild(ctx, parentNodeIds)
tmp, err := handleSelfChild(ctx, parentNodeIds...)
if err != nil {
logger.L().Error("", zap.Error(err))
return

View File

@@ -108,7 +108,7 @@ func (c *Controller) GetCommands(ctx *gin.Context) {
if info && !acl.IsAdmin(currentUser) {
//rs := make([]*acl.Resource, 0)
rs, err := acl.GetRoleResources(ctx, currentUser.Acl.Rid, acl.GetResourceTypeName(conf.RESOURCE_AUTHORIZATION))
rs, err := acl.GetRoleResources(ctx, currentUser.Acl.Rid, conf.RESOURCE_AUTHORIZATION)
if err != nil {
handleRemoteErr(ctx, err)
return
@@ -137,5 +137,5 @@ func (c *Controller) GetCommands(ctx *gin.Context) {
db = db.Order("name")
doGet[*model.Command](ctx, !info, db, acl.GetResourceTypeName(conf.RESOURCE_COMMAND))
doGet[*model.Command](ctx, !info, db, conf.RESOURCE_COMMAND)
}

View File

@@ -88,7 +88,7 @@ func write(sess *gsession.Session) {
sess.CliRw.Write(out)
}
if len(out) > 0 && strings.Contains(sess.Protocol, "ssh") {
if sess.SshRecoder != nil && len(out) > 0 && strings.Contains(sess.Protocol, "ssh") {
sess.SshRecoder.Write(out)
}
@@ -137,7 +137,9 @@ func HandleSsh(sess *gsession.Session) (err error) {
if checkTime(asset.AccessAuth) {
continue
}
writeErrMsg(sess, "invalid access time\n\n")
if sess.ShareId != 0 && time.Now().Before(sess.ShareEnd) {
continue
}
return &ApiError{Code: ErrAccessTime}
case closeBy := <-chs.CloseChan:
writeErrMsg(sess, "closed by admin\n\n")
@@ -223,6 +225,9 @@ func handleGuacd(sess *gsession.Session) (err error) {
if checkTime(asset.AccessAuth) {
continue
}
if sess.ShareId != 0 && time.Now().Before(sess.ShareEnd) {
continue
}
return &ApiError{Code: ErrAccessTime}
case closeBy := <-chs.CloseChan:
return &ApiError{Code: ErrAdminClose, Data: map[string]any{"admin": closeBy}}
@@ -279,6 +284,12 @@ func DoConnect(ctx *gin.Context, ws *websocket.Conn) (sess *gsession.Session, er
Status: model.SESSIONSTATUS_ONLINE,
ShareId: cast.ToInt(ctx.Value("shareId")),
}
if sess.ShareId != 0 {
sess.ShareEnd, _ = ctx.Value("shareEnd").(time.Time)
if err, _ = ctx.Value("shareErr").(error); err != nil {
return
}
}
if sess.IsSsh() {
w, h := cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h"))
sess.SshParser = gsession.NewParser(sess.SessionId, w, h)

View File

@@ -80,7 +80,7 @@ func doCreate[T model.Model](ctx *gin.Context, needAcl bool, md T, resourceType
resourceId := 0
if needAcl {
_, ok := any(md).(*model.Node)
resourceId, err = acl.CreateGrantAcl(ctx, currentUser, resourceType, lo.Ternary(ok, cast.ToString(md.GetId()), md.GetName()))
resourceId, err = acl.CreateGrantAcl(ctx, currentUser, resourceType, md.GetName()+lo.Ternary(ok, time.Now().Format(time.RFC3339), ""))
if err != nil {
handleRemoteErr(ctx, err)
return
@@ -102,6 +102,11 @@ func doCreate[T model.Model](ctx *gin.Context, needAcl bool, md T, resourceType
handleRemoteErr(ctx, err)
return
}
case *model.Node:
if err = acl.UpdateResource(ctx, currentUser.GetUid(), resourceId, map[string]string{"name": cast.ToString(md.GetId())}); err != nil {
handleRemoteErr(ctx, err)
return
}
}
if err = tx.Create(&model.History{
@@ -248,7 +253,7 @@ func doUpdate[T model.Model](ctx *gin.Context, needAcl bool, md T, resourceType
return
}
if needAcl {
if hasPerm(ctx, md, resourceType, acl.WRITE) {
if !hasPerm(ctx, md, resourceType, acl.WRITE) {
ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{"perm": acl.WRITE}})
return
}
@@ -316,13 +321,10 @@ func doGet[T any](ctx *gin.Context, needAcl bool, dbFind *gorm.DB, resourceType
currentUser, _ := acl.GetSessionFromCtx(ctx)
if needAcl && !acl.IsAdmin(currentUser) {
var rs []*acl.Resource
rs, err = acl.GetRoleResources(ctx, currentUser.Acl.Rid, resourceType)
if err != nil {
handleRemoteErr(ctx, err)
if dbFind, err = handleAcl[T](ctx, dbFind, resourceType); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
return
}
dbFind = dbFind.Where("resource_id IN ?", lo.Map(rs, func(r *acl.Resource, _ int) int { return r.ResourceId }))
}
dbCount := dbFind.Session(&gorm.Session{})
@@ -352,6 +354,13 @@ func doGet[T any](ctx *gin.Context, needAcl bool, dbFind *gorm.DB, resourceType
return
}
// switch t := any(list).(type) {
// case []*model.Node:
// if t, err = nodePostHookParent(ctx, t); err != nil {
// return
// }
// list = lo.Map(t, func(n *model.Node, _ int) T { return any(n).(T) })
// }
for _, hook := range postHooks {
if hook == nil {
continue
@@ -474,17 +483,21 @@ func hasPerm[T model.Model](ctx context.Context, md T, resourceTypeName, action
return true
}
pid := 0
pids := make([]int, 0)
switch t := any(md).(type) {
case *model.Asset:
pid = t.ParentId
pids, _ = handleSelfParent(ctx, t.ParentId)
case *model.Node:
pid = t.ParentId
pids, _ = handleSelfParent(ctx, t.ParentId)
}
if pid > 0 {
if len(pids) > 0 {
res, _ := acl.GetRoleResources(ctx, currentUser.GetRid(), conf.RESOURCE_NODE)
if _, ok := lo.Find(res, func(r *acl.Resource) bool { return r.ResourceId == pid }); ok {
resId2perms := lo.SliceToMap(res, func(r *acl.Resource) (int, []string) { return r.ResourceId, r.Permissions })
resId2perms, _ = handleSelfChildPerms(ctx, resId2perms)
nodes, _ := getAllNodes(ctx)
id2resId := lo.SliceToMap(nodes, func(n *model.Node) (int, int) { return n.Id, n.ResourceId })
if lo.ContainsBy(pids, func(pid int) bool { return lo.Contains(resId2perms[id2resId[pid]], action) }) {
return true
}
}
@@ -548,3 +561,79 @@ func handlePermissions[T any](ctx *gin.Context, data []T, resourceTypeName strin
return
}
func handleAcl[T any](ctx *gin.Context, dbFind *gorm.DB, resourceType string) (db *gorm.DB, err error) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
resIds, err := acl.GetRoleResourceIds(ctx, currentUser.Acl.Rid, resourceType)
if err != nil {
return
}
switch any(*new(T)).(type) {
case *model.Node:
db, err = handleNodeIds(ctx, dbFind, resIds)
case *model.Asset:
db, err = handleAssetIds(ctx, dbFind, resIds)
default:
db = dbFind.Where("resource_id IN ?", resIds)
}
return
}
func handleNodeIds(ctx *gin.Context, dbFind *gorm.DB, resIds []int) (db *gorm.DB, err error) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
nodes, err := getAllNodes(ctx)
if err != nil {
return
}
nodes = lo.Filter(nodes, func(n *model.Node, _ int) bool { return lo.Contains(resIds, n.ResourceId) })
ids := lo.Map(nodes, func(n *model.Node, _ int) int { return n.Id })
if ids, err = handleSelfChild(ctx, ids...); err != nil {
return
}
assetResIds, err := acl.GetRoleResources(ctx, currentUser.GetRid(), conf.RESOURCE_ASSET)
if err != nil {
return
}
assets := make([]*model.AssetIdPid, 0)
if err = mysql.DB.Model(assets).Where("resource_id IN ?", assetResIds).Find(&assets).Error; err != nil {
return
}
ids = append(ids, lo.Map(assets, func(a *model.AssetIdPid, _ int) int { return a.ParentId })...)
ids, err = handleSelfParent(ctx, ids...)
if err != nil {
return
}
db = dbFind.Where("id IN ?", ids)
return
}
func handleAssetIds(ctx *gin.Context, dbFind *gorm.DB, resIds []int) (db *gorm.DB, err error) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
nodes, err := getAllNodes(ctx)
if err != nil {
return
}
nodeResIds, err := acl.GetRoleResourceIds(ctx, currentUser.GetRid(), conf.RESOURCE_NODE)
if err != nil {
return
}
nodes = lo.Filter(nodes, func(n *model.Node, _ int) bool { return lo.Contains(nodeResIds, n.ResourceId) })
nodeIds := lo.Map(nodes, func(n *model.Node, _ int) int { return n.Id })
if nodeIds, err = handleSelfChild(ctx, nodeIds...); err != nil {
return
}
d := mysql.DB.Where("resource_id IN ?", resIds).Or("parent_id IN?", nodeIds)
db = dbFind.Where(d)
return
}

View File

@@ -159,5 +159,5 @@ func (c *Controller) GetGateways(ctx *gin.Context) {
db = db.Order("name")
doGet(ctx, !info, db, acl.GetResourceTypeName(conf.RESOURCE_GATEWAY), gatewayPostHooks...)
doGet(ctx, !info, db, conf.RESOURCE_GATEWAY, gatewayPostHooks...)
}

View File

@@ -24,7 +24,7 @@ import (
const (
kFmtAllNodes = "allNodes"
kFmtNodeIds = "assetIds-%d"
kFmtNodeIds = "nodeIds-%d"
)
var (
@@ -41,7 +41,7 @@ var (
// @Router /node [post]
func (c *Controller) CreateNode(ctx *gin.Context) {
redis.RC.Del(ctx, kFmtAllNodes)
doCreate(ctx, false, &model.Node{}, "")
doCreate(ctx, true, &model.Node{}, conf.RESOURCE_NODE)
}
// DeleteNode godoc
@@ -52,7 +52,7 @@ func (c *Controller) CreateNode(ctx *gin.Context) {
// @Router /node/:id [delete]
func (c *Controller) DeleteNode(ctx *gin.Context) {
redis.RC.Del(ctx, kFmtAllNodes)
doDelete(ctx, false, &model.Node{}, conf.RESOURCE_NODE, nodeDcs...)
doDelete(ctx, true, &model.Node{}, conf.RESOURCE_NODE, nodeDcs...)
}
// UpdateNode godoc
@@ -64,7 +64,7 @@ func (c *Controller) DeleteNode(ctx *gin.Context) {
// @Router /node/:id [put]
func (c *Controller) UpdateNode(ctx *gin.Context) {
redis.RC.Del(ctx, kFmtAllNodes)
doUpdate(ctx, false, &model.Node{}, conf.RESOURCE_NODE, nodePreHooks...)
doUpdate(ctx, true, &model.Node{}, conf.RESOURCE_NODE, nodePreHooks...)
}
// GetNodes godoc
@@ -114,12 +114,16 @@ func (c *Controller) GetNodes(ctx *gin.Context) {
if err != nil {
return
}
if ids, err = handleSelfChild(ctx, ids...); err != nil {
return
}
if ids, err = handleSelfParent(ctx, ids...); err != nil {
return
}
db = db.Where("id IN ?", ids)
}
db = db.Order("name DESC")
doGet(ctx, !info, db, acl.GetResourceTypeName(conf.RESOURCE_NODE), nodePostHooks...)
doGet(ctx, !info, db, conf.RESOURCE_NODE, nodePostHooks...)
}
func nodePreHookCheckCycle(ctx *gin.Context, data *model.Node) {
@@ -149,22 +153,11 @@ func nodePostHookCountAsset(ctx *gin.Context, data []*model.Node) {
assets := make([]*model.AssetIdPid, 0)
db := mysql.DB.Model(&model.Asset{})
if !isAdmin {
authorizationResourceIds, err := getAutorizationResourceIds(ctx)
assetIds, err := GetAssetIdsByAuthorization(ctx)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
ids := make([]int, 0)
if err = mysql.DB.
Model(&model.Authorization{}).
Where("resource_id IN ?", authorizationResourceIds).
Distinct().
Pluck("asset_id", &ids).
Error; err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
return
}
db = db.Where("id IN ?", ids)
db = db.Where("id IN ?", assetIds)
}
if err := db.Find(&assets).Error; err != nil {
logger.L().Error("node posthookfailed asset count", zap.Error(err))
@@ -213,6 +206,31 @@ func nodePostHookHasChild(ctx *gin.Context, data []*model.Node) {
}
}
// func nodePostHookParent(ctx *gin.Context, data []*model.Node) (res []*model.Node, err error) {
// defer func() {
// if err != nil {
// ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
// }
// }()
// nodes, err := getAllNodes(ctx)
// if err != nil {
// return
// }
// ids, err := handleSelfParent(ctx, lo.Map(data, func(n *model.Node, _ int) int { return n.Id })...)
// if err != nil {
// return
// }
// nm := lo.SliceToMap(nodes, func(n *model.Node) (int, *model.Node) { return n.Id, n })
// for _, id := range ids {
// if x, ok := nm[id]; ok {
// data = append(data, x)
// }
// }
// res = lo.UniqBy(data, func(n *model.Node) int { return n.Id })
// sort.Slice(res, func(i, j int) bool { return res[i].Name < res[j].Name })
// return
// }
func nodeDelHook(ctx *gin.Context, id int) {
noChild := true
noChild = noChild && errors.Is(mysql.DB.Model(&model.Node{}).Select("id").Where("parent_id = ?", id).First(map[string]any{}).Error, gorm.ErrRecordNotFound)
@@ -247,7 +265,7 @@ func handleNoSelfChild(ctx context.Context, id int) (ids []int, err error) {
return
}
func handleSelfParent(ctx context.Context, id int) (ids []int, err error) {
func handleSelfParent(ctx context.Context, ids ...int) (res []int, err error) {
nodes, err := getAllNodes(ctx)
if err != nil {
return
@@ -257,21 +275,26 @@ func handleSelfParent(ctx context.Context, id int) (ids []int, err error) {
for _, n := range nodes {
g[n.ParentId] = append(g[n.ParentId], n.Id)
}
t := make([]int, 0)
var dfs func(int)
dfs = func(x int) {
ids = append(ids, x)
t = append(t, x)
if lo.Contains(ids, x) {
res = append(res, t...)
}
for _, y := range g[x] {
dfs(y)
}
t = t[:len(t)-1]
}
dfs(id)
dfs(0)
ids = append(lo.Without(lo.Keys(g), ids...), id)
res = lo.Uniq(res)
return
}
func handleSelfChild(ctx context.Context, parentIds []int) (ids []int, err error) {
func handleSelfChild(ctx context.Context, ids ...int) (res []int, err error) {
nodes, err := getAllNodes(ctx)
if err != nil {
return
@@ -284,36 +307,34 @@ func handleSelfChild(ctx context.Context, parentIds []int) (ids []int, err error
var dfs func(int, bool)
dfs = func(x int, b bool) {
if b {
ids = append(ids, x)
res = append(res, x)
}
for _, y := range g[x] {
dfs(y, b || lo.Contains(parentIds, x))
dfs(y, b || lo.Contains(ids, x))
}
}
dfs(0, false)
res = append(res, ids...)
return
}
func GetNodeIdsByAuthorization(ctx *gin.Context) (ids []int, err error) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
authIds, err := getAuthorizationIds(ctx)
if err != nil {
return
}
ctx.Set(kAuthorizationIds, authIds)
k := fmt.Sprintf(kFmtNodeIds, currentUser.GetUid())
if err = redis.Get(ctx, k, &ids); err == nil {
return
}
parentNodeIds, _, _ := getIdsByAuthorizationIds(ctx)
ids, err = handleSelfChild(ctx, parentNodeIds)
assetIds, err := GetAssetIdsByAuthorization(ctx)
if err != nil {
return
}
if err = mysql.DB.Model(&model.Asset{}).Where("id IN ?", assetIds).Pluck("parent_id", &ids).Error; err != nil {
return
}
redis.SetEx(ctx, k, ids, time.Minute)

View File

@@ -139,8 +139,7 @@ func (c *Controller) ConnectShare(ctx *gin.Context) {
}
return
}); err != nil {
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrBadRequest, Data: map[string]any{"err": err}})
return
ctx.Set("shareErr", &ApiError{Code: ErrInvalidArgument, Data: map[string]any{"err": err}})
}
ctx.Params = lo.Filter(ctx.Params, func(p gin.Param, _ int) bool {
@@ -151,6 +150,7 @@ func (c *Controller) ConnectShare(ctx *gin.Context) {
ctx.Params = append(ctx.Params, gin.Param{Key: "protocol", Value: cast.ToString(share.Protocol)})
ctx.Set("shareId", share.Id)
ctx.Set("session", &acl.Session{})
ctx.Set("shareEnd", share.End)
c.Connect(ctx)
}

View File

@@ -223,11 +223,11 @@ func (c *Controller) StatCountOfUser(ctx *gin.Context) {
isAdmin := acl.IsAdmin(currentUser)
db := mysql.DB.Model(&model.Asset{})
if !isAdmin {
authorizationResourceIds, err := getAutorizationResourceIds(ctx)
assetIds, err := GetAssetIdsByAuthorization(ctx)
if err != nil {
return err
}
db = db.Where("id IN (?)", mysql.DB.Model(&model.Authorization{}).Select("asset_id").Where("resource_id IN ?", authorizationResourceIds))
db = db.Where("id IN ?", assetIds)
}
return db.Count(&stat.TotalAsset).Error
})

View File

@@ -84,7 +84,6 @@ type AclConfig struct {
Url string `yaml:"url"`
AppId string `yaml:"appId"`
SecretKey string `yaml:"secretKey"`
ResourceNames []*KV `yaml:"resourceNames"`
}
type AesConfig struct {
@@ -137,13 +136,3 @@ type ConfigYaml struct {
Auth Auth `yaml:"auth"`
SecretKey string `yaml:"secretKey"`
}
func GetResourceTypeName(key string) (val string) {
for _, kv := range Cfg.Auth.Acl.ResourceNames {
if kv.Key == key {
val = kv.Value
return
}
}
return
}

View File

@@ -4,15 +4,14 @@ import (
"fmt"
"time"
"github.com/spf13/cast"
"gorm.io/plugin/soft_delete"
)
type Authorization struct {
Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"`
AssetId *int `json:"asset_id" gorm:"column:asset_id;uniqueIndex:uidx_aand"`
AccountId *int `json:"account_id" gorm:"column:account_id;uniqueIndex:uidx_aand"`
NodeId *int `json:"node_id" gorm:"column:node_id;uniqueIndex:uidx_aand"`
AssetId int `json:"asset_id" gorm:"column:asset_id;uniqueIndex:uidx_aand"`
AccountId int `json:"account_id" gorm:"column:account_id;uniqueIndex:uidx_aand"`
NodeId int `json:"node_id" gorm:"column:node_id;uniqueIndex:uidx_aand"`
Rids Slice[int] `json:"rids" gorm:"column:rids"`
ResourceId int `json:"resource_id" gorm:"column:resource_id"`
@@ -28,7 +27,7 @@ func (m *Authorization) TableName() string {
}
func (m *Authorization) GetName() string {
return fmt.Sprintf("%d-%d-%d", cast.ToInt(m.AssetId), cast.ToInt(m.AccountId), cast.ToInt(m.NodeId))
return fmt.Sprintf("%d-%d-%d", m.AssetId, m.AccountId, m.NodeId)
}
func (m *Authorization) GetId() int {
@@ -40,9 +39,9 @@ type InfoModel interface {
}
type AuthorizationIds struct {
AssetId *int `json:"asset_id" gorm:"column:asset_id"`
AccountId *int `json:"account_id" gorm:"column:account_id"`
NodeId *int `json:"node_id" gorm:"column:node_id"`
AssetId int `json:"asset_id" gorm:"column:asset_id"`
AccountId int `json:"account_id" gorm:"column:account_id"`
NodeId int `json:"node_id" gorm:"column:node_id"`
}
func (m *AuthorizationIds) TableName() string {

View File

@@ -48,7 +48,7 @@ func (m *Node) SetResourceId(resourceId int) {
}
func (m *Node) GetResourceId() int {
return 0
return m.ResourceId
}
func (m *Node) GetName() string {
return m.Name

View File

@@ -121,6 +121,7 @@ type Session struct {
IdleTk *time.Ticker `json:"-" gorm:"-"`
SshRecoder *Asciinema `json:"-" gorm:"-"`
SshParser *Parser `json:"-" gorm:"-"`
ShareEnd time.Time `json:"-" gorm:"-"`
}
func (m *Session) HasMonitors() (has bool) {

View File

@@ -878,7 +878,7 @@ func (m *Model) updateSuggestions() {
for _, s := range m.suggestions {
suggestion := string(s)
if strings.HasPrefix(strings.ToLower(suggestion), strings.ToLower(string(m.value))) {
if strings.HasPrefix(suggestion, string(m.value)) {
matches = append(matches, []rune(suggestion))
}
}

View File

@@ -259,11 +259,11 @@ func (m *view) refresh() {
m.combines = make(map[string][3]int)
for _, auth := range auths {
asset, ok := assetMap[*auth.AssetId]
asset, ok := assetMap[auth.AssetId]
if !ok {
continue
}
account, ok := accountMap[*auth.AccountId]
account, ok := accountMap[auth.AccountId]
if !ok {
continue
}

View File

@@ -43,19 +43,6 @@ auth:
appId: 5867e079dfd1437e9ae07576ab24b391
secretKey: 2qlTA4z@#KyigJLYHGrev?0WD6hjX*8E
url: http://acl-api:5000/api/v1
resourceNames:
- key: account
value: account
- key: asset
value: asset
- key: command
value: command
- key: gateway
value: gateway
- key: authorization
value: authorization
- key: node
value: node
aes:
key: thisis32bitlongpassphraseimusing
iv: 0123456789abcdef