refactor(backend): authorization v2

This commit is contained in:
pycook
2025-07-16 18:11:04 +08:00
parent d8387323dd
commit 7e2c667fc4
48 changed files with 10593 additions and 554 deletions

View File

@@ -0,0 +1,329 @@
package controller
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
"gorm.io/gorm"
"github.com/veops/oneterm/internal/acl"
"github.com/veops/oneterm/internal/model"
"github.com/veops/oneterm/internal/service"
myErrors "github.com/veops/oneterm/pkg/errors"
)
// CreateAuthorizationV2 godoc
//
// @Tags authorization_v2
// @Param authorization body model.AuthorizationV2 true "authorization rule"
// @Success 200 {object} HttpResponse
// @Router /authorization_v2 [post]
func (c *Controller) CreateAuthorizationV2(ctx *gin.Context) {
auth := &model.AuthorizationV2{}
err := ctx.ShouldBindBodyWithJSON(auth)
if err != nil {
ctx.AbortWithError(http.StatusBadRequest, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
authV2Service := service.NewAuthorizationV2Service()
// Check permissions
currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) {
ctx.AbortWithError(http.StatusForbidden, &myErrors.ApiError{Code: myErrors.ErrNoPerm, Data: map[string]any{"perm": acl.GRANT}})
return
}
// Create the rule
err = authV2Service.CreateRule(ctx, auth)
if err != nil {
if ctx.IsAborted() {
return
}
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal, Data: map[string]any{"err": err}})
return
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: map[string]any{
"id": auth.GetId(),
},
})
}
// UpdateAuthorizationV2 godoc
//
// @Tags authorization_v2
// @Param id path int true "authorization id"
// @Param authorization body model.AuthorizationV2 true "authorization rule"
// @Success 200 {object} HttpResponse
// @Router /authorization_v2/:id [put]
func (c *Controller) UpdateAuthorizationV2(ctx *gin.Context) {
authId := cast.ToInt(ctx.Param("id"))
auth := &model.AuthorizationV2{}
err := ctx.ShouldBindBodyWithJSON(auth)
if err != nil {
ctx.AbortWithError(http.StatusBadRequest, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
auth.Id = authId
authV2Service := service.NewAuthorizationV2Service()
// Check permissions
currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) {
ctx.AbortWithError(http.StatusForbidden, &myErrors.ApiError{Code: myErrors.ErrNoPerm, Data: map[string]any{"perm": acl.GRANT}})
return
}
// Update the rule
err = authV2Service.UpdateRule(ctx, auth)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ctx.AbortWithError(http.StatusBadRequest, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument, Data: map[string]any{"err": err}})
} else {
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal, Data: map[string]any{"err": err}})
}
return
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: map[string]any{
"id": auth.GetId(),
},
})
}
// DeleteAuthorizationV2 godoc
//
// @Tags authorization_v2
// @Param id path int true "authorization id"
// @Success 200 {object} HttpResponse
// @Router /authorization_v2/:id [delete]
func (c *Controller) DeleteAuthorizationV2(ctx *gin.Context) {
authId := cast.ToInt(ctx.Param("id"))
authV2Service := service.NewAuthorizationV2Service()
// Check permissions
currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) {
ctx.AbortWithError(http.StatusForbidden, &myErrors.ApiError{Code: myErrors.ErrNoPerm, Data: map[string]any{"perm": acl.GRANT}})
return
}
// Delete the rule
if err := authV2Service.DeleteRule(ctx, authId); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ctx.AbortWithError(http.StatusBadRequest, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument, Data: map[string]any{"err": err}})
} else {
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal, Data: map[string]any{"err": err}})
}
return
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: map[string]any{
"id": authId,
},
})
}
// GetAuthorizationV2 godoc
//
// @Tags authorization_v2
// @Param id path int true "authorization id"
// @Success 200 {object} HttpResponse{data=model.AuthorizationV2}
// @Router /authorization_v2/:id [get]
func (c *Controller) GetAuthorizationV2(ctx *gin.Context) {
authId := cast.ToInt(ctx.Param("id"))
authV2Service := service.NewAuthorizationV2Service()
// Get the rule
auth, err := authV2Service.GetRuleById(ctx, authId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ctx.AbortWithError(http.StatusNotFound, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument, Data: map[string]any{"err": err}})
} else {
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal, Data: map[string]any{"err": err}})
}
return
}
if auth == nil {
ctx.AbortWithError(http.StatusNotFound, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument, Data: map[string]any{"err": "rule not found"}})
return
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: auth,
})
}
// CloneAuthorizationV2 godoc
//
// @Tags authorization_v2
// @Param id path int true "source authorization id"
// @Param body body object true "clone request"
// @Success 200 {object} HttpResponse{data=model.AuthorizationV2}
// @Router /authorization_v2/:id/clone [post]
func (c *Controller) CloneAuthorizationV2(ctx *gin.Context) {
sourceId := cast.ToInt(ctx.Param("id"))
// Parse request body
var req struct {
Name string `json:"name" binding:"required"`
}
err := ctx.ShouldBindBodyWithJSON(&req)
if err != nil {
ctx.AbortWithError(http.StatusBadRequest, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument, Data: map[string]any{"err": err}})
return
}
authV2Service := service.NewAuthorizationV2Service()
// Check permissions
currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) {
ctx.AbortWithError(http.StatusForbidden, &myErrors.ApiError{Code: myErrors.ErrNoPerm, Data: map[string]any{"perm": acl.GRANT}})
return
}
// Clone the rule
clonedRule, err := authV2Service.CloneRule(ctx, sourceId, req.Name)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ctx.AbortWithError(http.StatusNotFound, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument, Data: map[string]any{"err": "source rule not found"}})
} else {
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal, Data: map[string]any{"err": err}})
}
return
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: clonedRule,
})
}
// GetAuthorizationsV2 godoc
//
// @Tags authorization_v2
// @Param page_index query int false "page index"
// @Param page_size query int false "page size"
// @Param enabled query bool false "filter by enabled status"
// @Param search query string false "search by name or description"
// @Success 200 {object} HttpResponse{data=ListData{list=[]model.AuthorizationV2}}
// @Router /authorization_v2 [get]
func (c *Controller) GetAuthorizationsV2(ctx *gin.Context) {
pageIndex := cast.ToInt(ctx.DefaultQuery("page_index", "1"))
pageSize := cast.ToInt(ctx.DefaultQuery("page_size", "20"))
enabled := ctx.Query("enabled")
search := ctx.Query("search")
// Check permissions
currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) {
ctx.AbortWithError(http.StatusForbidden, &myErrors.ApiError{Code: myErrors.ErrNoPerm, Data: map[string]any{"perm": acl.READ}})
return
}
authV2Service := service.NewAuthorizationV2Service()
// Build query with filters
db, err := authV2Service.BuildQuery(ctx)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal, Data: map[string]any{"err": err}})
return
}
// Apply filters
if enabled != "" {
db = db.Where("enabled = ?", enabled == "true")
}
if search != "" {
db = db.Where("name LIKE ? OR description LIKE ?", "%"+search+"%", "%"+search+"%")
}
// Get total count
var count int64
if err := db.Count(&count).Error; err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal, Data: map[string]any{"err": err}})
return
}
// Apply pagination
offset := (pageIndex - 1) * pageSize
var auths []*model.AuthorizationV2
if err := db.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&auths).Error; err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal, Data: map[string]any{"err": err}})
return
}
// Convert to slice of any type
authsAny := make([]any, 0, len(auths))
for _, a := range auths {
authsAny = append(authsAny, a)
}
result := &ListData{
Count: count,
List: authsAny,
}
ctx.JSON(http.StatusOK, HttpResponse{
Data: result,
})
}
// CheckPermissionV2 godoc
//
// @Tags authorization_v2
// @Param request body CheckPermissionRequest true "permission check request"
// @Success 200 {object} HttpResponse{data=model.AuthResult}
// @Router /authorization_v2/check [post]
func (c *Controller) CheckPermissionV2(ctx *gin.Context) {
var req CheckPermissionRequest
if err := ctx.ShouldBindBodyWithJSON(&req); err != nil {
ctx.AbortWithError(http.StatusBadRequest, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument})
return
}
result, err := service.DefaultAuthService.CheckPermission(ctx, req.NodeId, req.AssetId, req.AccountId, req.Action)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal})
return
}
ctx.JSON(http.StatusOK, HttpResponse{Data: result})
}
// CheckPermissionRequest represents a permission check request
type CheckPermissionRequest struct {
NodeId int `json:"node_id" binding:"gte=0"`
AssetId int `json:"asset_id" binding:"gte=0"`
AccountId int `json:"account_id" binding:"gte=0"`
Action model.AuthAction `json:"action" binding:"required"`
}
// AuthorizationV2 hooks
var (
authV2PreHooks = []preHook[*model.AuthorizationV2]{
func(ctx *gin.Context, data *model.AuthorizationV2) {
authV2Service := service.NewAuthorizationV2Service()
if err := authV2Service.ValidateRule(ctx, data); err != nil {
ctx.AbortWithError(http.StatusBadRequest, &myErrors.ApiError{Code: myErrors.ErrInvalidArgument, Data: map[string]any{"err": err.Error()}})
}
},
}
authV2PostHooks = []postHook[*model.AuthorizationV2]{
func(ctx *gin.Context, data []*model.AuthorizationV2) {
// Add any post-processing logic here
},
}
)