mirror of https://github.com/veypi/OneAuth.git
upgrade
parent
ced7cc6a07
commit
8fa01c4c52
@ -1,162 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
)
|
||||
|
||||
// InitOrgPolicies 为组织初始化默认策略和角色
|
||||
func InitOrgPolicies(orgID string) error {
|
||||
// 创建默认策略
|
||||
policies := getDefaultPolicies()
|
||||
for _, policy := range policies {
|
||||
var count int64
|
||||
cfg.DB().Model(&models.Policy{}).Where("code = ?", policy.Code).Count(&count)
|
||||
if count == 0 {
|
||||
if err := cfg.DB().Create(&policy).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建默认角色
|
||||
roles := getDefaultRoles()
|
||||
for _, role := range roles {
|
||||
role.OrgID = orgID
|
||||
var count int64
|
||||
cfg.DB().Model(&models.Role{}).Where("code = ? AND org_id = ?", role.Code, orgID).Count(&count)
|
||||
if count == 0 {
|
||||
if err := cfg.DB().Create(&role).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDefaultPolicies 获取默认策略列表
|
||||
func getDefaultPolicies() []models.Policy {
|
||||
return []models.Policy{
|
||||
{
|
||||
Code: "user:read",
|
||||
Name: "读取用户信息",
|
||||
Resource: "user",
|
||||
Action: "read",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
{
|
||||
Code: "user:update",
|
||||
Name: "更新用户信息",
|
||||
Resource: "user",
|
||||
Action: "update",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Condition: "owner",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
{
|
||||
Code: "role:manage",
|
||||
Name: "管理角色",
|
||||
Resource: "role",
|
||||
Action: "*",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Condition: "admin",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
{
|
||||
Code: "policy:manage",
|
||||
Name: "管理策略",
|
||||
Resource: "policy",
|
||||
Action: "*",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Condition: "admin",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
{
|
||||
Code: "org:read",
|
||||
Name: "读取组织信息",
|
||||
Resource: "org",
|
||||
Action: "read",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
{
|
||||
Code: "org:update",
|
||||
Name: "更新组织信息",
|
||||
Resource: "org",
|
||||
Action: "update",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Condition: "admin",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
{
|
||||
Code: "org:delete",
|
||||
Name: "删除组织",
|
||||
Resource: "org",
|
||||
Action: "delete",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Condition: "owner",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
{
|
||||
Code: "member:manage",
|
||||
Name: "管理成员",
|
||||
Resource: "org_member",
|
||||
Action: "*",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Condition: "admin",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
{
|
||||
Code: "resource:read",
|
||||
Name: "读取资源",
|
||||
Resource: "resource",
|
||||
Action: "read",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
{
|
||||
Code: "resource:write",
|
||||
Name: "写入资源",
|
||||
Resource: "resource",
|
||||
Action: "create,update,delete",
|
||||
Effect: models.PolicyEffectAllow,
|
||||
Condition: "owner",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// getDefaultRoles 获取默认角色列表
|
||||
func getDefaultRoles() []models.Role {
|
||||
return []models.Role{
|
||||
{
|
||||
Name: "管理员",
|
||||
Code: models.RoleCodeAdmin,
|
||||
Description: "组织管理员,可以管理成员、角色和策略",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
IsSystem: true,
|
||||
},
|
||||
{
|
||||
Name: "开发者",
|
||||
Code: models.RoleCodeDeveloper,
|
||||
Description: "开发者,可以创建和管理资源",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
IsSystem: true,
|
||||
},
|
||||
{
|
||||
Name: "只读用户",
|
||||
Code: models.RoleCodeViewer,
|
||||
Description: "只读访问权限",
|
||||
Scope: models.PolicyScopeOrg,
|
||||
IsSystem: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type CreateRequest struct {
|
||||
Code string `json:"code" src:"json" desc:"策略代码"`
|
||||
Name string `json:"name" src:"json" desc:"策略名称"`
|
||||
Description string `json:"description,omitempty" src:"json" desc:"描述"`
|
||||
Resource string `json:"resource" src:"json" desc:"资源: user/org/resource/*"`
|
||||
Action string `json:"action" src:"json" desc:"操作: create/read/update/delete/*"`
|
||||
Effect string `json:"effect" src:"json" desc:"效果: allow/deny"`
|
||||
Condition string `json:"condition,omitempty" src:"json" desc:"条件: owner/org_member"`
|
||||
Scope string `json:"scope" src:"json" desc:"作用域: platform/org/resource"`
|
||||
}
|
||||
|
||||
func create(x *vigo.X, req *CreateRequest) (*models.Policy, error) {
|
||||
// 检查代码是否已存在
|
||||
var count int64
|
||||
cfg.DB().Model(&models.Policy{}).Where("code = ?", req.Code).Count(&count)
|
||||
if count > 0 {
|
||||
return nil, vigo.ErrArgInvalid.WithString("policy code already exists")
|
||||
}
|
||||
|
||||
policy := &models.Policy{
|
||||
Code: req.Code,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Resource: req.Resource,
|
||||
Action: req.Action,
|
||||
Effect: req.Effect,
|
||||
Condition: req.Condition,
|
||||
Scope: req.Scope,
|
||||
}
|
||||
|
||||
if policy.Effect == "" {
|
||||
policy.Effect = models.PolicyEffectAllow
|
||||
}
|
||||
if policy.Scope == "" {
|
||||
policy.Scope = models.PolicyScopeOrg
|
||||
}
|
||||
|
||||
if err := cfg.DB().Create(policy).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type DeleteRequest struct {
|
||||
PolicyID string `src:"path@policy_id" desc:"策略ID"`
|
||||
}
|
||||
|
||||
func del(x *vigo.X, req *DeleteRequest) error {
|
||||
var policy models.Policy
|
||||
if err := cfg.DB().First(&policy, "id = ?", req.PolicyID).Error; err != nil {
|
||||
return vigo.ErrNotFound
|
||||
}
|
||||
|
||||
// 系统策略不允许删除
|
||||
if policy.Scope == models.PolicyScopePlatform {
|
||||
return vigo.ErrForbidden.WithString("system policies cannot be deleted")
|
||||
}
|
||||
|
||||
if err := cfg.DB().Delete(&policy).Error; err != nil {
|
||||
return vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type GetRequest struct {
|
||||
PolicyID string `src:"path@policy_id" desc:"策略ID"`
|
||||
}
|
||||
|
||||
func get(x *vigo.X, req *GetRequest) (*models.Policy, error) {
|
||||
var policy models.Policy
|
||||
if err := cfg.DB().First(&policy, "id = ?", req.PolicyID).Error; err != nil {
|
||||
return nil, vigo.ErrNotFound
|
||||
}
|
||||
return &policy, nil
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/api/middleware"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
|
||||
func init() {
|
||||
// 列表和详情 - 需要读取权限
|
||||
Router.Get("/", middleware.Permission("policy", "list"), "策略列表", list)
|
||||
Router.Get("/{policy_id}", middleware.Permission("policy", "read"), "获取策略详情", get)
|
||||
|
||||
// 创建、更新、删除 - 需要管理权限
|
||||
Router.Post("/", middleware.Permission("policy", "create"), "创建策略", create)
|
||||
Router.Patch("/{policy_id}", middleware.Permission("policy", "update"), "更新策略", patch)
|
||||
Router.Delete("/{policy_id}", middleware.Permission("policy", "delete"), "删除策略", del)
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type ListRequest struct {
|
||||
Page int `json:"page" src:"query" default:"1"`
|
||||
PageSize int `json:"page_size" src:"query" default:"20"`
|
||||
Resource string `json:"resource" src:"query" desc:"资源类型筛选"`
|
||||
Scope string `json:"scope" src:"query" desc:"作用域筛选"`
|
||||
}
|
||||
|
||||
type ListResponse struct {
|
||||
Items []models.Policy `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
func list(x *vigo.X, req *ListRequest) (*ListResponse, error) {
|
||||
db := cfg.DB().Model(&models.Policy{})
|
||||
|
||||
if req.Resource != "" {
|
||||
db = db.Where("resource = ?", req.Resource)
|
||||
}
|
||||
if req.Scope != "" {
|
||||
db = db.Where("scope = ?", req.Scope)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
var policies []models.Policy
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
if err := db.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&policies).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
totalPages := int(total) / req.PageSize
|
||||
if int(total)%req.PageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &ListResponse{
|
||||
Items: policies,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type PatchRequest struct {
|
||||
PolicyID string `src:"path@policy_id" desc:"策略ID"`
|
||||
Name *string `json:"name,omitempty" src:"json" desc:"策略名称"`
|
||||
Description *string `json:"description,omitempty" src:"json" desc:"描述"`
|
||||
Effect *string `json:"effect,omitempty" src:"json" desc:"效果: allow/deny"`
|
||||
Condition *string `json:"condition,omitempty" src:"json" desc:"条件"`
|
||||
}
|
||||
|
||||
func patch(x *vigo.X, req *PatchRequest) (*models.Policy, error) {
|
||||
var policy models.Policy
|
||||
if err := cfg.DB().First(&policy, "id = ?", req.PolicyID).Error; err != nil {
|
||||
return nil, vigo.ErrNotFound
|
||||
}
|
||||
|
||||
// 系统策略不允许修改
|
||||
if policy.Scope == models.PolicyScopePlatform {
|
||||
return nil, vigo.ErrForbidden.WithString("system policies cannot be modified")
|
||||
}
|
||||
|
||||
updates := make(map[string]any)
|
||||
if req.Name != nil {
|
||||
updates["name"] = *req.Name
|
||||
}
|
||||
if req.Description != nil {
|
||||
updates["description"] = *req.Description
|
||||
}
|
||||
if req.Effect != nil {
|
||||
updates["effect"] = *req.Effect
|
||||
}
|
||||
if req.Condition != nil {
|
||||
updates["condition"] = *req.Condition
|
||||
}
|
||||
|
||||
if err := cfg.DB().Model(&policy).Updates(updates).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
return &policy, nil
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type CreateRequest struct {
|
||||
OrgID string `json:"org_id" src:"json" desc:"组织ID"`
|
||||
Name string `json:"name" src:"json" desc:"角色名称"`
|
||||
Code string `json:"code" src:"json" desc:"角色代码"`
|
||||
Description string `json:"description,omitempty" src:"json" desc:"描述"`
|
||||
PolicyIDs []string `json:"policy_ids,omitempty" src:"json" desc:"策略ID列表"`
|
||||
}
|
||||
|
||||
func create(x *vigo.X, req *CreateRequest) (*models.Role, error) {
|
||||
// 检查同一组织内代码是否已存在
|
||||
var count int64
|
||||
cfg.DB().Model(&models.Role{}).Where("org_id = ? AND code = ?", req.OrgID, req.Code).Count(&count)
|
||||
if count > 0 {
|
||||
return nil, vigo.ErrArgInvalid.WithString("role code already exists in this organization")
|
||||
}
|
||||
|
||||
// 转换策略ID列表为字符串
|
||||
policyIDsStr := ""
|
||||
for i, id := range req.PolicyIDs {
|
||||
if i > 0 {
|
||||
policyIDsStr += ","
|
||||
}
|
||||
policyIDsStr += id
|
||||
}
|
||||
|
||||
role := &models.Role{
|
||||
OrgID: req.OrgID,
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
Description: req.Description,
|
||||
PolicyIDs: policyIDsStr,
|
||||
Scope: models.PolicyScopeOrg,
|
||||
}
|
||||
|
||||
if err := cfg.DB().Create(role).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type DeleteRequest struct {
|
||||
RoleID string `src:"path@role_id" desc:"角色ID"`
|
||||
}
|
||||
|
||||
func del(x *vigo.X, req *DeleteRequest) error {
|
||||
var role models.Role
|
||||
if err := cfg.DB().First(&role, "id = ?", req.RoleID).Error; err != nil {
|
||||
return vigo.ErrNotFound
|
||||
}
|
||||
|
||||
// 系统角色不允许删除
|
||||
if role.IsSystem {
|
||||
return vigo.ErrForbidden.WithString("system roles cannot be deleted")
|
||||
}
|
||||
|
||||
if err := cfg.DB().Delete(&role).Error; err != nil {
|
||||
return vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type GetRequest struct {
|
||||
RoleID string `src:"path@role_id" desc:"角色ID"`
|
||||
}
|
||||
|
||||
type RoleDetail struct {
|
||||
models.Role
|
||||
Policies []models.Policy `json:"policies"`
|
||||
}
|
||||
|
||||
func get(x *vigo.X, req *GetRequest) (*RoleDetail, error) {
|
||||
var role models.Role
|
||||
if err := cfg.DB().First(&role, "id = ?", req.RoleID).Error; err != nil {
|
||||
return nil, vigo.ErrNotFound
|
||||
}
|
||||
|
||||
// 解析策略ID列表
|
||||
policies := []models.Policy{}
|
||||
// TODO: 根据 role.PolicyIDs 查询策略列表
|
||||
|
||||
return &RoleDetail{
|
||||
Role: role,
|
||||
Policies: policies,
|
||||
}, nil
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/api/middleware"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
|
||||
func init() {
|
||||
// 列表和详情 - 需要读取权限
|
||||
Router.Get("/", middleware.Permission("role", "list"), "角色列表", list)
|
||||
Router.Get("/{role_id}", middleware.Permission("role", "read"), "获取角色详情", get)
|
||||
|
||||
// 创建、更新、删除 - 需要管理权限
|
||||
Router.Post("/", middleware.Permission("role", "create"), "创建角色", create)
|
||||
Router.Patch("/{role_id}", middleware.Permission("role", "update"), "更新角色", patch)
|
||||
Router.Delete("/{role_id}", middleware.Permission("role", "delete"), "删除角色", del)
|
||||
|
||||
// 角色策略管理 - 需要角色管理权限
|
||||
Router.Get("/{role_id}/policies", middleware.Permission("role", "read"), "获取角色策略", listPolicies)
|
||||
Router.Post("/{role_id}/policies", middleware.Permission("role", "update"), "为角色添加策略", addPolicy)
|
||||
Router.Delete("/{role_id}/policies/{policy_id}", middleware.Permission("role", "update"), "从角色移除策略", removePolicy)
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type ListRequest struct {
|
||||
Page int `json:"page" src:"query" default:"1"`
|
||||
PageSize int `json:"page_size" src:"query" default:"20"`
|
||||
OrgID string `json:"org_id" src:"query" desc:"组织ID筛选"`
|
||||
}
|
||||
|
||||
type ListResponse struct {
|
||||
Items []models.Role `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
func list(x *vigo.X, req *ListRequest) (*ListResponse, error) {
|
||||
db := cfg.DB().Model(&models.Role{})
|
||||
|
||||
if req.OrgID != "" {
|
||||
db = db.Where("org_id = ?", req.OrgID)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
var roles []models.Role
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
if err := db.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&roles).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
totalPages := int(total) / req.PageSize
|
||||
if int(total)%req.PageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &ListResponse{
|
||||
Items: roles,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type PatchRequest struct {
|
||||
RoleID string `src:"path@role_id" desc:"角色ID"`
|
||||
Name *string `json:"name,omitempty" src:"json" desc:"角色名称"`
|
||||
Description *string `json:"description,omitempty" src:"json" desc:"描述"`
|
||||
PolicyIDs []string `json:"policy_ids,omitempty" src:"json" desc:"策略ID列表"`
|
||||
}
|
||||
|
||||
func patch(x *vigo.X, req *PatchRequest) (*models.Role, error) {
|
||||
var role models.Role
|
||||
if err := cfg.DB().First(&role, "id = ?", req.RoleID).Error; err != nil {
|
||||
return nil, vigo.ErrNotFound
|
||||
}
|
||||
|
||||
// 系统角色不允许修改
|
||||
if role.IsSystem {
|
||||
return nil, vigo.ErrForbidden.WithString("system roles cannot be modified")
|
||||
}
|
||||
|
||||
updates := make(map[string]any)
|
||||
if req.Name != nil {
|
||||
updates["name"] = *req.Name
|
||||
}
|
||||
if req.Description != nil {
|
||||
updates["description"] = *req.Description
|
||||
}
|
||||
if req.PolicyIDs != nil {
|
||||
policyIDsStr := ""
|
||||
for i, id := range req.PolicyIDs {
|
||||
if i > 0 {
|
||||
policyIDsStr += ","
|
||||
}
|
||||
policyIDsStr += id
|
||||
}
|
||||
updates["policy_ids"] = policyIDsStr
|
||||
}
|
||||
|
||||
if err := cfg.DB().Model(&role).Updates(updates).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
return &role, nil
|
||||
}
|
||||
@ -1,129 +0,0 @@
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
// ListPoliciesRequest 获取角色策略请求
|
||||
type ListPoliciesRequest struct {
|
||||
RoleID string `src:"path@role_id" desc:"角色ID"`
|
||||
}
|
||||
|
||||
// listPolicies 获取角色关联的策略列表
|
||||
func listPolicies(x *vigo.X, req *ListPoliciesRequest) ([]models.Policy, error) {
|
||||
var role models.Role
|
||||
if err := cfg.DB().First(&role, "id = ?", req.RoleID).Error; err != nil {
|
||||
return nil, vigo.ErrNotFound
|
||||
}
|
||||
|
||||
if role.PolicyIDs == "" {
|
||||
return []models.Policy{}, nil
|
||||
}
|
||||
|
||||
// 解析策略ID列表
|
||||
policyIDs := strings.Split(role.PolicyIDs, ",")
|
||||
|
||||
var policies []models.Policy
|
||||
if err := cfg.DB().Where("id IN ?", policyIDs).Find(&policies).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// AddPolicyRequest 添加策略请求
|
||||
type AddPolicyRequest struct {
|
||||
RoleID string `src:"path@role_id" desc:"角色ID"`
|
||||
PolicyID string `json:"policy_id" src:"json" desc:"策略ID"`
|
||||
}
|
||||
|
||||
// addPolicy 为角色添加策略
|
||||
func addPolicy(x *vigo.X, req *AddPolicyRequest) (*models.Role, error) {
|
||||
var role models.Role
|
||||
if err := cfg.DB().First(&role, "id = ?", req.RoleID).Error; err != nil {
|
||||
return nil, vigo.ErrNotFound
|
||||
}
|
||||
|
||||
// 验证策略是否存在
|
||||
var policy models.Policy
|
||||
if err := cfg.DB().First(&policy, "id = ?", req.PolicyID).Error; err != nil {
|
||||
return nil, vigo.ErrArgInvalid.WithString("policy not found")
|
||||
}
|
||||
|
||||
// 解析现有策略ID
|
||||
existingIDs := map[string]bool{}
|
||||
if role.PolicyIDs != "" {
|
||||
for _, id := range strings.Split(role.PolicyIDs, ",") {
|
||||
existingIDs[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
if existingIDs[req.PolicyID] {
|
||||
return nil, vigo.ErrArgInvalid.WithString("policy already added to this role")
|
||||
}
|
||||
|
||||
// 添加新策略ID
|
||||
if role.PolicyIDs != "" {
|
||||
role.PolicyIDs += ","
|
||||
}
|
||||
role.PolicyIDs += req.PolicyID
|
||||
|
||||
if err := cfg.DB().Model(&role).Update("policy_ids", role.PolicyIDs).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// RemovePolicyRequest 移除策略请求
|
||||
type RemovePolicyRequest struct {
|
||||
RoleID string `src:"path@role_id" desc:"角色ID"`
|
||||
PolicyID string `src:"path@policy_id" desc:"策略ID"`
|
||||
}
|
||||
|
||||
// removePolicy 从角色移除策略
|
||||
func removePolicy(x *vigo.X, req *RemovePolicyRequest) (*models.Role, error) {
|
||||
var role models.Role
|
||||
if err := cfg.DB().First(&role, "id = ?", req.RoleID).Error; err != nil {
|
||||
return nil, vigo.ErrNotFound
|
||||
}
|
||||
|
||||
// 解析现有策略ID
|
||||
if role.PolicyIDs == "" {
|
||||
return nil, vigo.ErrArgInvalid.WithString("role has no policies")
|
||||
}
|
||||
|
||||
existingIDs := strings.Split(role.PolicyIDs, ",")
|
||||
newIDs := []string{}
|
||||
found := false
|
||||
|
||||
for _, id := range existingIDs {
|
||||
if id == req.PolicyID {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
newIDs = append(newIDs, id)
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, vigo.ErrArgInvalid.WithString("policy not found in this role")
|
||||
}
|
||||
|
||||
// 更新策略ID列表
|
||||
role.PolicyIDs = strings.Join(newIDs, ",")
|
||||
|
||||
if err := cfg.DB().Model(&role).Update("policy_ids", role.PolicyIDs).Error; err != nil {
|
||||
return nil, vigo.ErrInternalServer.WithError(err)
|
||||
}
|
||||
|
||||
return &role, nil
|
||||
}
|
||||
@ -0,0 +1,610 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-02-14 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
// Auth 权限管理接口
|
||||
type Auth interface {
|
||||
// ========== 中间件生成 ==========
|
||||
// 基础权限检查
|
||||
Perm(permissionID string) func(*vigo.X) error
|
||||
|
||||
// 资源所有者权限
|
||||
PermWithOwner(permissionID, ownerKey string) func(*vigo.X) error
|
||||
|
||||
// 满足任一权限
|
||||
PermAny(permissionIDs []string) func(*vigo.X) error
|
||||
|
||||
// 满足所有权限
|
||||
PermAll(permissionIDs []string) func(*vigo.X) error
|
||||
|
||||
// ========== 权限管理 ==========
|
||||
// 授予角色
|
||||
GrantRole(ctx context.Context, req models.GrantRoleRequest) error
|
||||
|
||||
// 撤销角色
|
||||
RevokeRole(ctx context.Context, userID, orgID, roleCode string) error
|
||||
|
||||
// 授予特定资源权限
|
||||
GrantResourcePerm(ctx context.Context, req models.GrantResourcePermRequest) error
|
||||
|
||||
// 撤销特定资源权限
|
||||
RevokeResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error
|
||||
|
||||
// 撤销用户所有权限
|
||||
RevokeAll(ctx context.Context, userID, orgID string) error
|
||||
|
||||
// ========== 权限查询 ==========
|
||||
// 检查权限
|
||||
CheckPermission(ctx context.Context, req models.CheckPermRequest) (bool, error)
|
||||
|
||||
// 列出用户权限
|
||||
ListUserPermissions(ctx context.Context, userID, orgID string) ([]models.UserPermissionResult, error)
|
||||
|
||||
// 列出资源授权用户
|
||||
ListResourceUsers(ctx context.Context, orgID, permissionID, resourceID string) ([]models.ResourceUser, error)
|
||||
}
|
||||
|
||||
// 全局 Auth 工厂
|
||||
var Factory = &authFactory{
|
||||
apps: make(map[string]*appAuth),
|
||||
}
|
||||
|
||||
// VBaseAuth vbase 自身的权限管理实例
|
||||
// 由 vbase 包在初始化时注入
|
||||
var (
|
||||
VBaseAuth = Factory.New("vb", models.AppConfig{
|
||||
Name: "VBase",
|
||||
Description: "VBase 基础设施",
|
||||
DefaultRoles: []models.RoleDefinition{
|
||||
{Code: "admin", Name: "管理员", Policies: []string{"*:*"}},
|
||||
{Code: "user", Name: "普通用户", Policies: []string{"user:read", "user:update"}},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
type authFactory struct {
|
||||
apps map[string]*appAuth // appKey -> auth实例
|
||||
}
|
||||
|
||||
// New 创建权限管理实例(注册应用)
|
||||
func (f *authFactory) New(appKey string, config models.AppConfig) Auth {
|
||||
if _, exists := f.apps[appKey]; exists {
|
||||
return f.apps[appKey]
|
||||
}
|
||||
|
||||
auth := &appAuth{
|
||||
appKey: appKey,
|
||||
config: config,
|
||||
}
|
||||
f.apps[appKey] = auth
|
||||
return auth
|
||||
}
|
||||
|
||||
// Init 初始化所有注册的权限配置
|
||||
// - 检查不同 app 之间是否有冲突
|
||||
// - 同步 Permission 到数据库
|
||||
// - 建立预设角色
|
||||
func (f *authFactory) Init() error {
|
||||
for appKey, auth := range f.apps {
|
||||
if err := auth.init(); err != nil {
|
||||
return fmt.Errorf("failed to init auth for %s: %w", appKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// appAuth 单个应用的权限管理
|
||||
type appAuth struct {
|
||||
appKey string
|
||||
config models.AppConfig
|
||||
}
|
||||
|
||||
// init 初始化应用的权限配置
|
||||
func (a *appAuth) init() error {
|
||||
// 1. 同步权限定义到数据库
|
||||
for _, permDef := range a.extractPermissions() {
|
||||
var perm models.Permission
|
||||
err := cfg.DB().Where("id = ?", permDef.ID).First(&perm).Error
|
||||
if err != nil {
|
||||
// 不存在则创建
|
||||
perm = permDef
|
||||
if err := cfg.DB().Create(&perm).Error; err != nil {
|
||||
return fmt.Errorf("failed to create permission %s: %w", permDef.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 创建系统预设角色
|
||||
for _, roleDef := range a.config.DefaultRoles {
|
||||
if err := a.initRole(roleDef); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractPermissions 从角色定义中提取所有权限
|
||||
func (a *appAuth) extractPermissions() []models.Permission {
|
||||
permMap := make(map[string]models.Permission)
|
||||
|
||||
for _, roleDef := range a.config.DefaultRoles {
|
||||
for _, policy := range roleDef.Policies {
|
||||
// policy 格式: "resource:action" 或 "*:*"
|
||||
parts := strings.Split(policy, ":")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
resource, action := parts[0], parts[1]
|
||||
permID := fmt.Sprintf("%s:%s:%s", a.appKey, resource, action)
|
||||
|
||||
if _, exists := permMap[permID]; !exists {
|
||||
permMap[permID] = models.Permission{
|
||||
ID: permID,
|
||||
AppKey: a.appKey,
|
||||
Resource: resource,
|
||||
Action: action,
|
||||
Description: fmt.Sprintf("%s %s on %s", a.config.Name, action, resource),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]models.Permission, 0, len(permMap))
|
||||
for _, perm := range permMap {
|
||||
result = append(result, perm)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// initRole 初始化系统预设角色
|
||||
func (a *appAuth) initRole(roleDef models.RoleDefinition) error {
|
||||
// 查找或创建系统角色
|
||||
var role models.Role
|
||||
err := cfg.DB().Where("code = ? AND org_id = ''", roleDef.Code).First(&role).Error
|
||||
if err != nil {
|
||||
// 创建新角色
|
||||
role = models.Role{
|
||||
OrgID: "",
|
||||
Code: roleDef.Code,
|
||||
Name: roleDef.Name,
|
||||
Description: roleDef.Description,
|
||||
IsSystem: true,
|
||||
Status: 1,
|
||||
}
|
||||
if err := cfg.DB().Create(&role).Error; err != nil {
|
||||
return fmt.Errorf("failed to create role %s: %w", roleDef.Code, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 同步角色权限
|
||||
for _, policy := range roleDef.Policies {
|
||||
parts := strings.Split(policy, ":")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
resource, action := parts[0], parts[1]
|
||||
permID := fmt.Sprintf("%s:%s:%s", a.appKey, resource, action)
|
||||
|
||||
// 检查关联是否存在
|
||||
var count int64
|
||||
cfg.DB().Model(&models.RolePermission{}).
|
||||
Where("role_id = ? AND permission_id = ?", role.ID, permID).
|
||||
Count(&count)
|
||||
|
||||
if count == 0 {
|
||||
rp := models.RolePermission{
|
||||
RoleID: role.ID,
|
||||
PermissionID: permID,
|
||||
Condition: "none",
|
||||
}
|
||||
if err := cfg.DB().Create(&rp).Error; err != nil {
|
||||
return fmt.Errorf("failed to create role permission: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ========== 中间件实现 ==========
|
||||
|
||||
func (a *appAuth) Perm(permissionID string) func(*vigo.X) error {
|
||||
return func(x *vigo.X) error {
|
||||
userID := getUserID(x)
|
||||
if userID == "" {
|
||||
return vigo.ErrUnauthorized
|
||||
}
|
||||
|
||||
orgID := getOrgID(x)
|
||||
|
||||
ok, err := a.CheckPermission(x.Context(), models.CheckPermRequest{
|
||||
UserID: userID,
|
||||
OrgID: orgID,
|
||||
PermissionID: permissionID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return vigo.ErrForbidden
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *appAuth) PermWithOwner(permissionID, ownerKey string) func(*vigo.X) error {
|
||||
return func(x *vigo.X) error {
|
||||
userID := getUserID(x)
|
||||
if userID == "" {
|
||||
return vigo.ErrUnauthorized
|
||||
}
|
||||
|
||||
orgID := getOrgID(x)
|
||||
|
||||
// 先检查是否有权限
|
||||
ok, err := a.CheckPermission(x.Context(), models.CheckPermRequest{
|
||||
UserID: userID,
|
||||
OrgID: orgID,
|
||||
PermissionID: permissionID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return vigo.ErrForbidden
|
||||
}
|
||||
|
||||
// 检查是否是所有者或管理员
|
||||
ownerID, _ := x.Get(ownerKey).(string)
|
||||
if ownerID == userID {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否是管理员
|
||||
isAdmin, _ := a.isAdmin(x.Context(), userID, orgID)
|
||||
if isAdmin {
|
||||
return nil
|
||||
}
|
||||
|
||||
return vigo.ErrForbidden
|
||||
}
|
||||
}
|
||||
|
||||
func (a *appAuth) PermAny(permissionIDs []string) func(*vigo.X) error {
|
||||
return func(x *vigo.X) error {
|
||||
userID := getUserID(x)
|
||||
if userID == "" {
|
||||
return vigo.ErrUnauthorized
|
||||
}
|
||||
|
||||
orgID := getOrgID(x)
|
||||
|
||||
for _, permID := range permissionIDs {
|
||||
ok, err := a.CheckPermission(x.Context(), models.CheckPermRequest{
|
||||
UserID: userID,
|
||||
OrgID: orgID,
|
||||
PermissionID: permID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return vigo.ErrForbidden
|
||||
}
|
||||
}
|
||||
|
||||
func (a *appAuth) PermAll(permissionIDs []string) func(*vigo.X) error {
|
||||
return func(x *vigo.X) error {
|
||||
userID := getUserID(x)
|
||||
if userID == "" {
|
||||
return vigo.ErrUnauthorized
|
||||
}
|
||||
|
||||
orgID := getOrgID(x)
|
||||
|
||||
for _, permID := range permissionIDs {
|
||||
ok, err := a.CheckPermission(x.Context(), models.CheckPermRequest{
|
||||
UserID: userID,
|
||||
OrgID: orgID,
|
||||
PermissionID: permID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return vigo.ErrForbidden
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 权限管理实现 ==========
|
||||
|
||||
func (a *appAuth) GrantRole(ctx context.Context, req models.GrantRoleRequest) error {
|
||||
// 查找角色
|
||||
var role models.Role
|
||||
query := cfg.DB().Where("code = ?", req.RoleCode)
|
||||
if req.OrgID != "" {
|
||||
query = query.Where("org_id = ?", req.OrgID)
|
||||
} else {
|
||||
query = query.Where("org_id = ''")
|
||||
}
|
||||
|
||||
if err := query.First(&role).Error; err != nil {
|
||||
return fmt.Errorf("role not found: %s", req.RoleCode)
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
var count int64
|
||||
cfg.DB().Model(&models.UserRole{}).
|
||||
Where("user_id = ? AND org_id = ? AND role_id = ?", req.UserID, req.OrgID, role.ID).
|
||||
Count(&count)
|
||||
|
||||
if count > 0 {
|
||||
return nil // 已存在
|
||||
}
|
||||
|
||||
userRole := models.UserRole{
|
||||
UserID: req.UserID,
|
||||
OrgID: req.OrgID,
|
||||
RoleID: role.ID,
|
||||
ExpireAt: req.ExpireAt,
|
||||
}
|
||||
|
||||
return cfg.DB().Create(&userRole).Error
|
||||
}
|
||||
|
||||
func (a *appAuth) RevokeRole(ctx context.Context, userID, orgID, roleCode string) error {
|
||||
var role models.Role
|
||||
query := cfg.DB().Where("code = ?", roleCode)
|
||||
if orgID != "" {
|
||||
query = query.Where("org_id = ?", orgID)
|
||||
} else {
|
||||
query = query.Where("org_id = ''")
|
||||
}
|
||||
|
||||
if err := query.First(&role).Error; err != nil {
|
||||
return nil // 角色不存在,无需撤销
|
||||
}
|
||||
|
||||
return cfg.DB().Where("user_id = ? AND org_id = ? AND role_id = ?", userID, orgID, role.ID).
|
||||
Delete(&models.UserRole{}).Error
|
||||
}
|
||||
|
||||
func (a *appAuth) GrantResourcePerm(ctx context.Context, req models.GrantResourcePermRequest) error {
|
||||
// 检查权限是否存在
|
||||
var perm models.Permission
|
||||
if err := cfg.DB().Where("id = ?", req.PermissionID).First(&perm).Error; err != nil {
|
||||
return fmt.Errorf("permission not found: %s", req.PermissionID)
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
var existing models.UserPermission
|
||||
err := cfg.DB().Where("user_id = ? AND org_id = ? AND permission_id = ? AND resource_id = ?",
|
||||
req.UserID, req.OrgID, req.PermissionID, req.ResourceID).
|
||||
First(&existing).Error
|
||||
|
||||
if err == nil {
|
||||
// 更新过期时间
|
||||
existing.ExpireAt = req.ExpireAt
|
||||
return cfg.DB().Save(&existing).Error
|
||||
}
|
||||
|
||||
userPerm := models.UserPermission{
|
||||
UserID: req.UserID,
|
||||
OrgID: req.OrgID,
|
||||
PermissionID: req.PermissionID,
|
||||
ResourceID: req.ResourceID,
|
||||
ExpireAt: req.ExpireAt,
|
||||
GrantedBy: req.GrantedBy,
|
||||
}
|
||||
|
||||
return cfg.DB().Create(&userPerm).Error
|
||||
}
|
||||
|
||||
func (a *appAuth) RevokeResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error {
|
||||
return cfg.DB().Where("user_id = ? AND org_id = ? AND permission_id = ? AND resource_id = ?",
|
||||
userID, orgID, permissionID, resourceID).
|
||||
Delete(&models.UserPermission{}).Error
|
||||
}
|
||||
|
||||
func (a *appAuth) RevokeAll(ctx context.Context, userID, orgID string) error {
|
||||
// 删除用户角色
|
||||
if err := cfg.DB().Where("user_id = ? AND org_id = ?", userID, orgID).
|
||||
Delete(&models.UserRole{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除用户特定权限
|
||||
if err := cfg.DB().Where("user_id = ? AND org_id = ?", userID, orgID).
|
||||
Delete(&models.UserPermission{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ========== 权限查询实现 ==========
|
||||
|
||||
func (a *appAuth) CheckPermission(ctx context.Context, req models.CheckPermRequest) (bool, error) {
|
||||
// 1. 检查用户是否有该权限的角色
|
||||
var roleIDs []string
|
||||
if err := cfg.DB().Model(&models.UserRole{}).
|
||||
Where("user_id = ? AND org_id = ? AND (expire_at IS NULL OR expire_at > ?)",
|
||||
req.UserID, req.OrgID, time.Now()).
|
||||
Pluck("role_id", &roleIDs).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(roleIDs) > 0 {
|
||||
// 检查这些角色是否有所需权限
|
||||
var count int64
|
||||
if err := cfg.DB().Model(&models.RolePermission{}).
|
||||
Where("role_id IN ? AND permission_id = ?", roleIDs, req.PermissionID).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
if count > 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查用户是否有特定的资源权限
|
||||
var userPermCount int64
|
||||
query := cfg.DB().Model(&models.UserPermission{}).
|
||||
Where("user_id = ? AND org_id = ? AND permission_id = ? AND (expire_at IS NULL OR expire_at > ?)",
|
||||
req.UserID, req.OrgID, req.PermissionID, time.Now())
|
||||
|
||||
if req.ResourceID != "" {
|
||||
query = query.Where("resource_id = ? OR resource_id = '*'", req.ResourceID)
|
||||
}
|
||||
|
||||
if err := query.Count(&userPermCount).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return userPermCount > 0, nil
|
||||
}
|
||||
|
||||
func (a *appAuth) ListUserPermissions(ctx context.Context, userID, orgID string) ([]models.UserPermissionResult, error) {
|
||||
result := make([]models.UserPermissionResult, 0)
|
||||
|
||||
// 1. 获取用户角色对应的权限
|
||||
var roleIDs []string
|
||||
if err := cfg.DB().Model(&models.UserRole{}).
|
||||
Where("user_id = ? AND org_id = ? AND (expire_at IS NULL OR expire_at > ?)",
|
||||
userID, orgID, time.Now()).
|
||||
Pluck("role_id", &roleIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(roleIDs) > 0 {
|
||||
var permIDs []string
|
||||
if err := cfg.DB().Model(&models.RolePermission{}).
|
||||
Where("role_id IN ?", roleIDs).
|
||||
Pluck("permission_id", &permIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, permID := range permIDs {
|
||||
result = append(result, models.UserPermissionResult{
|
||||
PermissionID: permID,
|
||||
ResourceID: "*",
|
||||
Actions: []string{"*"},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 获取用户特定资源权限
|
||||
var userPerms []models.UserPermission
|
||||
if err := cfg.DB().Where("user_id = ? AND org_id = ? AND (expire_at IS NULL OR expire_at > ?)",
|
||||
userID, orgID, time.Now()).
|
||||
Find(&userPerms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, up := range userPerms {
|
||||
result = append(result, models.UserPermissionResult{
|
||||
PermissionID: up.PermissionID,
|
||||
ResourceID: up.ResourceID,
|
||||
Actions: []string{"*"},
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *appAuth) ListResourceUsers(ctx context.Context, orgID, permissionID, resourceID string) ([]models.ResourceUser, error) {
|
||||
result := make([]models.ResourceUser, 0)
|
||||
|
||||
// 查询有该资源权限的用户
|
||||
var userPerms []models.UserPermission
|
||||
query := cfg.DB().Where("org_id = ? AND permission_id = ?", orgID, permissionID)
|
||||
if resourceID != "" {
|
||||
query = query.Where("resource_id = ? OR resource_id = '*'", resourceID)
|
||||
}
|
||||
|
||||
if err := query.Find(&userPerms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userMap := make(map[string][]string)
|
||||
for _, up := range userPerms {
|
||||
userMap[up.UserID] = append(userMap[up.UserID], "*")
|
||||
}
|
||||
|
||||
for userID, actions := range userMap {
|
||||
result = append(result, models.ResourceUser{
|
||||
UserID: userID,
|
||||
Actions: actions,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
func (a *appAuth) isAdmin(ctx context.Context, userID, orgID string) (bool, error) {
|
||||
// 检查用户是否有管理员角色
|
||||
var adminRoleIDs []string
|
||||
if err := cfg.DB().Model(&models.Role{}).
|
||||
Where("code = 'admin'").
|
||||
Pluck("id", &adminRoleIDs).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(adminRoleIDs) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := cfg.DB().Model(&models.UserRole{}).
|
||||
Where("user_id = ? AND org_id = ? AND role_id IN ?", userID, orgID, adminRoleIDs).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// 从 context 获取用户ID
|
||||
func getUserID(x *vigo.X) string {
|
||||
if userID, ok := x.Get("user_id").(string); ok {
|
||||
return userID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 从 context 获取组织ID
|
||||
func getOrgID(x *vigo.X) string {
|
||||
if orgID, ok := x.Get("org_id").(string); ok {
|
||||
return orgID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2025-03-04 16:08:06
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// Policy 策略定义
|
||||
type Policy struct {
|
||||
Base
|
||||
Code string `json:"code" gorm:"uniqueIndex;size:50;not null"`
|
||||
Name string `json:"name" gorm:"size:50;not null"`
|
||||
Description string `json:"description" gorm:"size:200"`
|
||||
Resource string `json:"resource" gorm:"size:100;not null"` // 资源: user/org/resource/*
|
||||
Action string `json:"action" gorm:"size:50;not null"` // 操作: create/read/update/delete/*
|
||||
Effect string `json:"effect" gorm:"size:10;not null"` // 效果: allow/deny
|
||||
Condition string `json:"condition" gorm:"size:500"` // 条件: "owner", "org_member"
|
||||
Scope string `json:"scope" gorm:"size:20;not null"` // 作用域: platform/org/resource
|
||||
}
|
||||
|
||||
func (Policy) TableName() string {
|
||||
return "policies"
|
||||
}
|
||||
|
||||
// Role 角色定义
|
||||
type Role struct {
|
||||
Base
|
||||
OrgID string `json:"org_id" gorm:"index;not null"`
|
||||
Name string `json:"name" gorm:"size:50;not null"`
|
||||
Code string `json:"code" gorm:"size:50;not null"`
|
||||
Description string `json:"description" gorm:"size:200"`
|
||||
PolicyIDs string `json:"policy_ids" gorm:"size:500"` // 逗号分隔的策略ID
|
||||
Scope string `json:"scope" gorm:"size:20;default:'org'"` // platform/org
|
||||
IsSystem bool `json:"is_system" gorm:"default:false"` // 是否系统预设角色
|
||||
}
|
||||
|
||||
func (Role) TableName() string {
|
||||
return "roles"
|
||||
}
|
||||
|
||||
// RolePolicy 角色策略关联表
|
||||
type RolePolicy struct {
|
||||
Base
|
||||
RoleID string `json:"role_id" gorm:"uniqueIndex:idx_role_policy;not null"`
|
||||
PolicyID string `json:"policy_id" gorm:"uniqueIndex:idx_role_policy;not null"`
|
||||
}
|
||||
|
||||
func (RolePolicy) TableName() string {
|
||||
return "role_policies"
|
||||
}
|
||||
|
||||
// 预设策略常量
|
||||
const (
|
||||
PolicyEffectAllow = "allow"
|
||||
PolicyEffectDeny = "deny"
|
||||
PolicyScopePlatform = "platform"
|
||||
PolicyScopeOrg = "org"
|
||||
PolicyScopeResource = "resource"
|
||||
)
|
||||
|
||||
// 预设角色常量
|
||||
const (
|
||||
RoleCodeOwner = "owner"
|
||||
RoleCodeAdmin = "admin"
|
||||
RoleCodeDeveloper = "developer"
|
||||
RoleCodeViewer = "viewer"
|
||||
)
|
||||
Loading…
Reference in New Issue