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

424 lines
13 KiB
Go

package controller
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"github.com/spf13/cast"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
"gorm.io/gorm"
"github.com/veops/oneterm/internal/acl"
"github.com/veops/oneterm/internal/model"
"github.com/veops/oneterm/internal/repository"
gsession "github.com/veops/oneterm/internal/session"
"github.com/veops/oneterm/pkg/config"
dbpkg "github.com/veops/oneterm/pkg/db"
"github.com/veops/oneterm/pkg/logger"
)
// UpsertAuthorization godoc
//
// @Tags authorization
// @Param authorization body model.Authorization true "authorization"
// @Success 200 {object} HttpResponse
// @Router /authorization [post]
func (c *Controller) UpsertAuthorization(ctx *gin.Context) {
auth := &model.Authorization{}
err := ctx.ShouldBindBodyWithJSON(auth)
if err != nil {
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
if err := dbpkg.DB.Transaction(func(tx *gorm.DB) error {
t := &model.Authorization{}
if err = tx.Model(t).
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
}
err = nil
} else {
auth.Id = t.Id
auth.ResourceId = t.ResourceId
}
if !hasPermAuthorization(ctx, auth, acl.GRANT) {
err = &ApiError{Code: ErrNoPerm, Data: map[string]any{"perm": acl.GRANT}}
ctx.AbortWithError(http.StatusForbidden, err)
return err
}
action := lo.Ternary(auth.Id > 0, model.ACTION_UPDATE, model.ACTION_CREATE)
return handleAuthorization(ctx, tx, action, nil, auth)
}); err != nil {
if ctx.IsAborted() {
return
}
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
return
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: map[string]any{
"id": auth.GetId(),
},
})
}
// DeleteAccount godoc
//
// @Tags authorization
// @Param id path int true "authorization id"
// @Success 200 {object} HttpResponse
// @Router /authorization/:id [delete]
func (c *Controller) DeleteAuthorization(ctx *gin.Context) {
auth := &model.Authorization{
Id: cast.ToInt(ctx.Param("id")),
}
if err := dbpkg.DB.Model(auth).Where("id=?", auth.Id).First(auth); err != nil {
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
if !hasPermAuthorization(ctx, auth, acl.GRANT) {
ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{"perm": acl.GRANT}})
return
}
if err := handleAuthorization(ctx, dbpkg.DB, model.ACTION_DELETE, nil, auth); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
return
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: map[string]any{
"id": auth.GetId(),
},
})
}
// GetAuthorizations godoc
//
// @Tags authorization
// @Param page_index query int true "page_index"
// @Param page_size query int true "page_size"
// @Param node_id query int false "node id"
// @Param asset_id query int false "asset id"
// @Param account_id query int false "account id"
// @Success 200 {object} HttpResponse{data=ListData{list=[]model.Account}}
// @Router /authorization [get]
func (c *Controller) GetAuthorizations(ctx *gin.Context) {
auth := &model.Authorization{
AssetId: cast.ToInt(ctx.Query("asset_id")),
AccountId: cast.ToInt(ctx.Query("account_id")),
NodeId: cast.ToInt(ctx.Query("node_id")),
}
db := dbpkg.DB.Model(auth)
for _, k := range []string{"node_id", "asset_id", "account_id"} {
q, _ := ctx.GetQuery(k)
db = db.Where(fmt.Sprintf("%s=?", k), cast.ToInt(q))
}
t := db.Session(&gorm.Session{})
if err := t.First(&auth).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
return
}
if !hasPermAuthorization(ctx, auth, acl.GRANT) {
ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{"perm": acl.GRANT}})
return
}
doGet[*model.Authorization](ctx, false, db, config.RESOURCE_AUTHORIZATION)
}
func getNodeAssetAccoutIdsByAction(ctx context.Context, action string) (nodeIds, assetIds, accountIds []int, err error) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
eg := &errgroup.Group{}
ch := make(chan bool)
eg.Go(func() (err error) {
defer close(ch)
res, err := acl.GetRoleResources(ctx, currentUser.GetRid(), config.RESOURCE_NODE)
if err != nil {
return
}
res = lo.Filter(res, func(r *acl.Resource, _ int) bool { return lo.Contains(r.Permissions, action) })
resIds := lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId })
nodes, err := repository.GetAllFromCacheDb(ctx, model.DefaultNode)
if err != nil {
return
}
nodes = lo.Filter(nodes, func(n *model.Node, _ int) bool { return lo.Contains(resIds, n.ResourceId) })
nodeIds = lo.Map(nodes, func(n *model.Node, _ int) int { return n.Id })
nodeIds, err = handleSelfChild(ctx, nodeIds...)
return
})
eg.Go(func() (err error) {
res, err := acl.GetRoleResources(ctx, currentUser.GetRid(), config.RESOURCE_ASSET)
if err != nil {
return
}
res = lo.Filter(res, func(r *acl.Resource, _ int) bool { return lo.Contains(r.Permissions, action) })
resIds := lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId })
<-ch
assets, err := repository.GetAllFromCacheDb(ctx, model.DefaultAsset)
if err != nil {
return
}
assets = lo.Filter(assets, func(a *model.Asset, _ int) bool {
return lo.Contains(resIds, a.ResourceId) || lo.Contains(nodeIds, a.ParentId)
})
assetIds = lo.Map(assets, func(a *model.Asset, _ int) int { return a.Id })
return
})
eg.Go(func() (err error) {
res, err := acl.GetRoleResources(ctx, currentUser.GetRid(), config.RESOURCE_ACCOUNT)
if err != nil {
return
}
res = lo.Filter(res, func(r *acl.Resource, _ int) bool { return lo.Contains(r.Permissions, action) })
resIds := lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId })
accounts, err := repository.GetAllFromCacheDb(ctx, model.DefaultAccount)
if err != nil {
return
}
accounts = lo.Filter(accounts, func(a *model.Account, _ int) bool { return lo.Contains(resIds, a.ResourceId) })
accountIds = lo.Map(accounts, func(a *model.Account, _ int) int { return a.Id })
return
})
err = eg.Wait()
return
}
func hasPermAuthorization(ctx context.Context, auth *model.Authorization, action string) (ok bool) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
if ok = acl.IsAdmin(currentUser); ok {
return
}
if auth == nil {
auth = &model.Authorization{}
}
nodeIds, assetIds, accountIds, err := getNodeAssetAccoutIdsByAction(ctx, action)
if err != nil {
return
}
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
}
func getAuthsByAsset(t *model.Asset) (data []*model.Authorization, err error) {
err = dbpkg.DB.Model(data).Where("asset_id=? AND account_id IN ? AND node_id=0", t.Id, lo.Without(lo.Keys(t.Authorization), 0)).Find(&data).Error
return
}
func handleAuthorization(ctx *gin.Context, tx *gorm.DB, action int, asset *model.Asset, auths ...*model.Authorization) (err error) {
defer repository.DeleteAllFromCacheDb(ctx, model.DefaultAuthorization)
currentUser, _ := acl.GetSessionFromCtx(ctx)
eg := &errgroup.Group{}
if asset != nil && asset.Id > 0 {
var pres []*model.Authorization
pres, err = getAuthsByAsset(asset)
if err != nil {
return
}
switch action {
case model.ACTION_CREATE:
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]}
})
case model.ACTION_DELETE:
auths = pres
case model.ACTION_UPDATE:
for _, pre := range pres {
p := pre
if v, ok := asset.Authorization[p.AccountId]; ok {
p.Rids = v
auths = append(auths, p)
} else {
eg.Go(func() (err error) {
if err = acl.DeleteResource(ctx, currentUser.GetUid(), p.ResourceId); err != nil {
return
}
if err = dbpkg.DB.Model(p).Where("id=?", p.Id).Delete(p).Error; err != nil {
return
}
return
})
}
}
preAccountsIds := lo.Map(pres, func(p *model.Authorization, _ int) int { return p.AccountId })
for k, v := range asset.Authorization {
if !lo.Contains(preAccountsIds, k) {
auths = append(auths, &model.Authorization{AssetId: asset.Id, AccountId: k, Rids: v})
}
}
}
}
for _, a := range lo.Filter(auths, func(item *model.Authorization, _ int) bool { return item != nil }) {
auth := a
switch action {
case model.ACTION_CREATE:
eg.Go(func() (err error) {
resourceId := 0
if resourceId, err = acl.CreateAcl(ctx, currentUser, config.RESOURCE_AUTHORIZATION, auth.GetName()); err != nil {
return
}
if err = acl.BatchGrantRoleResource(ctx, currentUser.GetUid(), auth.Rids, resourceId, []string{acl.READ}); err != nil {
return
}
auth.CreatorId = currentUser.GetUid()
auth.UpdaterId = currentUser.GetUid()
auth.ResourceId = resourceId
return tx.Create(auth).Error
})
case model.ACTION_DELETE:
eg.Go(func() (err error) {
return acl.DeleteResource(ctx, currentUser.GetUid(), auth.ResourceId)
})
case model.ACTION_UPDATE:
eg.Go(func() (err error) {
pre := &model.Authorization{}
if err = dbpkg.DB.Where("id=?", auth.GetId()).First(pre).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return
}
resourceId := 0
if resourceId, err = acl.CreateAcl(ctx, currentUser, config.RESOURCE_AUTHORIZATION, auth.GetName()); err != nil {
return
}
auth.ResourceId = resourceId
if err = tx.Create(auth).Error; err != nil {
return
}
}
revokeRids := lo.Without(pre.Rids, auth.Rids...)
if len(revokeRids) > 0 {
if err = acl.BatchRevokeRoleResource(ctx, currentUser.GetUid(), revokeRids, auth.ResourceId, []string{acl.READ}); err != nil {
return
}
}
grantRids := lo.Without(auth.Rids, pre.Rids...)
if len(grantRids) > 0 {
if err = acl.BatchGrantRoleResource(ctx, currentUser.GetUid(), grantRids, auth.ResourceId, []string{acl.READ}); err != nil {
return
}
}
return tx.Model(auth).Update("rids", auth.Rids).Error
})
}
}
err = eg.Wait()
return
}
func getAuthorizations(ctx *gin.Context) (res []*acl.Resource, err error) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
res, err = acl.GetRoleResources(ctx, currentUser.GetRid(), config.RESOURCE_AUTHORIZATION)
if err != nil {
return
}
return
}
func getAutorizationResourceIds(ctx *gin.Context) (resourceIds []int, err error) {
res, err := getAuthorizations(ctx)
if err != nil {
return
}
resourceIds = lo.Map(res, func(r *acl.Resource, _ int) int { return r.ResourceId })
return
}
func getAuthorizationIds(ctx *gin.Context) (authIds []*model.AuthorizationIds, err error) {
resourceIds, err := getAutorizationResourceIds(ctx)
if err != nil {
handleRemoteErr(ctx, err)
return
}
err = dbpkg.DB.Model(authIds).Where("resource_id IN ?", resourceIds).Find(&authIds).Error
return
}
func hasAuthorization(ctx *gin.Context, sess *gsession.Session) (ok bool) {
currentUser, _ := acl.GetSessionFromCtx(ctx)
if sess.ShareId != 0 {
return true
}
if ok = acl.IsAdmin(currentUser); ok {
return
}
if sess.Session.Asset == nil {
if err := dbpkg.DB.Model(sess.Session.Asset).Where("id=?", sess.AssetId).First(&sess.Session.Asset).Error; err != nil {
return
}
}
authIds, err := getAuthorizationIds(ctx)
if err != nil {
return
}
if ok = lo.ContainsBy(authIds, func(item *model.AuthorizationIds) bool {
return item.NodeId == 0 && item.AssetId == sess.AssetId && item.AccountId == sess.AccountId
}); ok {
return
}
ctx.Set(kAuthorizationIds, authIds)
nodeIds, assetIds, accountIds := getIdsByAuthorizationIds(ctx)
tmp, err := handleSelfChild(ctx, nodeIds...)
if err != nil {
logger.L().Error("", zap.Error(err))
return
}
nodeIds = append(nodeIds, tmp...)
if ok = lo.Contains(nodeIds, sess.Session.Asset.ParentId) || lo.Contains(assetIds, sess.AssetId) || lo.Contains(accountIds, sess.AccountId); ok {
return
}
ids, err := getAssetIdsByNodeAccount(ctx, nodeIds, accountIds)
if err != nil {
logger.L().Error("", zap.Error(err))
return
}
return lo.Contains(ids, sess.AssetId)
}