mirror of
https://github.com/veops/oneterm.git
synced 2025-10-08 16:50:05 +08:00
feat(api): permission
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/samber/lo"
|
||||
@@ -13,12 +15,104 @@ import (
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/veops/oneterm/acl"
|
||||
redis "github.com/veops/oneterm/cache"
|
||||
"github.com/veops/oneterm/conf"
|
||||
mysql "github.com/veops/oneterm/db"
|
||||
"github.com/veops/oneterm/logger"
|
||||
"github.com/veops/oneterm/model"
|
||||
gsession "github.com/veops/oneterm/session"
|
||||
)
|
||||
|
||||
const (
|
||||
kFmtAuthorizationIds = "AuthorizationIds-%d"
|
||||
kFmtHasAuthorization = "HasAuthorization-%d-%d-%d"
|
||||
)
|
||||
|
||||
// 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 := mysql.DB.Transaction(func(tx *gorm.DB) error {
|
||||
auth := &model.Authorization{}
|
||||
if err = tx.Model(auth).
|
||||
Where(fmt.Sprintf("node_id %s AND asset_id %s AND account_id %s",
|
||||
lo.Ternary(auth.NodeId == nil, "IS NULL", fmt.Sprintf("=%d", auth.NodeId)),
|
||||
lo.Ternary(auth.AssetId == nil, "IS NULL", fmt.Sprintf("=%d", auth.AssetId)),
|
||||
lo.Ternary(auth.AccountId == nil, "IS NULL", fmt.Sprintf("=%d", auth.AccountId)),
|
||||
)).
|
||||
FirstOrCreate(auth).Error; err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
action := lo.Ternary(auth.Id > 0, model.ACTION_UPDATE, model.ACTION_CREATE)
|
||||
return handleAuthorization(ctx, tx, action, 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(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 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 := handleAuthorization(ctx, mysql.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) {
|
||||
db := mysql.DB.Model(&model.Authorization{})
|
||||
for _, k := range []string{"node_id", "asset_id", "account_id"} {
|
||||
q, ok := ctx.GetQuery(k)
|
||||
if ok {
|
||||
db = db.Where(fmt.Sprintf("%s IN ?", k), lo.Map(strings.Split(q, ","), func(s string, _ int) int { return cast.ToInt(s) }))
|
||||
} else {
|
||||
db = db.Where(fmt.Sprintf("%s IS NULL", k))
|
||||
}
|
||||
}
|
||||
|
||||
doGet[*model.Authorization](ctx, false, db, acl.GetResourceTypeName(conf.RESOURCE_AUTHORIZATION))
|
||||
}
|
||||
|
||||
func getAuthsByAsset(t *model.Asset) (data []*model.Authorization, err error) {
|
||||
db := mysql.DB.Model(data)
|
||||
for accountId := range t.Authorization {
|
||||
@@ -57,7 +151,8 @@ func handleAuthorization(ctx *gin.Context, tx *gorm.DB, action int, asset *model
|
||||
}
|
||||
}
|
||||
|
||||
for _, auth := range lo.Filter(auths, func(item *model.Authorization, _ int) bool { return item != nil }) {
|
||||
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) {
|
||||
@@ -103,29 +198,14 @@ func handleAuthorization(ctx *gin.Context, tx *gorm.DB, action int, asset *model
|
||||
return
|
||||
}
|
||||
|
||||
func sameAuthorization(old, new model.Map[int, model.Slice[int]]) bool {
|
||||
if len(old) != len(new) {
|
||||
return false
|
||||
}
|
||||
ks := lo.Uniq(append(lo.Keys(old), lo.Keys(new)...))
|
||||
for _, k := range ks {
|
||||
if len(old[k]) != len(new[k]) {
|
||||
return false
|
||||
}
|
||||
o, n := make([]int, 0, len(old[k])), make([]int, 0, len(new[k]))
|
||||
copy(o, old[k])
|
||||
copy(n, new[k])
|
||||
sort.Ints(o)
|
||||
sort.Ints(n)
|
||||
if !reflect.DeepEqual(o, n) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getAutorizationResourceIds(ctx *gin.Context) (resourceIds []int, err error) {
|
||||
currentUser, _ := acl.GetSessionFromCtx(ctx)
|
||||
|
||||
k := fmt.Sprintf(kFmtAuthorizationIds, currentUser.GetUid())
|
||||
if err = redis.Get(ctx, k, &resourceIds); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var rs []*acl.Resource
|
||||
rs, err = acl.GetRoleResources(ctx, currentUser.Acl.Rid, conf.RESOURCE_AUTHORIZATION)
|
||||
if err != nil {
|
||||
@@ -133,97 +213,63 @@ func getAutorizationResourceIds(ctx *gin.Context) (resourceIds []int, err error)
|
||||
}
|
||||
resourceIds = lo.Map(rs, func(r *acl.Resource, _ int) int { return r.ResourceId })
|
||||
|
||||
redis.SetEx(ctx, k, resourceIds, time.Minute)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func hasAuthorization(ctx *gin.Context, assetId, accountId int) (ok bool) {
|
||||
if cast.ToString(ctx.Value("shareId")) != "" {
|
||||
func hasAuthorization(ctx *gin.Context, sess *gsession.Session) (ok bool) {
|
||||
currentUser, _ := acl.GetSessionFromCtx(ctx)
|
||||
|
||||
if sess.ShareId != 0 {
|
||||
return true
|
||||
}
|
||||
ids, err := getAutorizationResourceIds(ctx)
|
||||
|
||||
k := fmt.Sprintf(kFmtHasAuthorization, currentUser.GetUid(), sess.AccountId, sess.AssetId)
|
||||
if err := redis.Get(ctx, k, &ok); err == nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
redis.SetEx(ctx, k, ok, time.Minute)
|
||||
}()
|
||||
|
||||
if ok = acl.IsAdmin(currentUser); ok {
|
||||
return
|
||||
}
|
||||
|
||||
if sess.Session.Asset == nil {
|
||||
if err := mysql.DB.Model(sess.Session.Asset).First(&sess.Session.Asset, sess.AssetId).Error; err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
authIds, err := getAuthorizationIds(ctx)
|
||||
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 {
|
||||
return
|
||||
}
|
||||
ctx.Set(kAuthorizationIds, authIds)
|
||||
|
||||
parentNodeIds, assetIds, accountIds := getIdsByAuthorizationIds(ctx)
|
||||
tmp, err := handleSelfChild(ctx, parentNodeIds)
|
||||
if err != nil {
|
||||
logger.L().Error("", zap.Error(err))
|
||||
return
|
||||
}
|
||||
cnt := int64(0)
|
||||
err = mysql.DB.Model(&model.Authorization{}).
|
||||
Where("asset_id =? AND account_id =? AND resource_id IN (?)", assetId, accountId, ids).
|
||||
Count(&cnt).Error
|
||||
parentNodeIds = append(parentNodeIds, tmp...)
|
||||
if ok = lo.Contains(parentNodeIds, sess.Session.Asset.ParentId) || lo.Contains(assetIds, sess.AssetId) || lo.Contains(accountIds, sess.AccountId); ok {
|
||||
return
|
||||
}
|
||||
|
||||
ids, err := getAssetIdsByNodeAccount(ctx, parentNodeIds, accountIds)
|
||||
if err != nil {
|
||||
logger.L().Error("", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
return cnt > 0
|
||||
}
|
||||
|
||||
// CreateAccount godoc
|
||||
//
|
||||
// @Tags authorization
|
||||
// @Param authorization body model.Authorization true "authorization"
|
||||
// @Success 200 {object} HttpResponse
|
||||
// @Router /authorization [post]
|
||||
func (c *Controller) CreateAuthorization(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 := handleAuthorization(ctx, mysql.DB, model.ACTION_CREATE, 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(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 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 := handleAuthorization(ctx, mysql.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(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAccount godoc
|
||||
//
|
||||
// @Tags authorization
|
||||
// @Param id path int true "authorization id"
|
||||
// @Param authorization body model.Authorization true "authorization"
|
||||
// @Success 200 {object} HttpResponse
|
||||
// @Router /authorization/:id [put]
|
||||
func (c *Controller) UpdateAuthorization(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
|
||||
}
|
||||
auth.Id = cast.ToInt(ctx.Param("id"))
|
||||
if err := handleAuthorization(ctx, mysql.DB, model.ACTION_UPDATE, 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(),
|
||||
},
|
||||
})
|
||||
return lo.Contains(ids, sess.AssetId)
|
||||
}
|
||||
|
Reference in New Issue
Block a user