Add Session model, SessionService, and SessionRepository to track user login sessions and enable token revocation on auth-critical events. - Introduce explicit TokenType (access/refresh) in JWT claims to prevent refresh token misuse via access token endpoints - Add SessionID field to JWT claims, enabling stateless JWT validation against revoked sessions - Replace legacy Auth middleware with RequireAuth/OptionalAuth pipeline that validates token type, account status, and session validity - Implement session revocation on password change, reset, user ban/inactive, and explicit logout - Add Principal cache with active invalidation for banned/role-changed users - Fix IDOR vulnerability: GetMessagesByCursor now validates currentUserID is conversation participant via GetParticipantStrict - Add group member visibility checks: announcements, group info, member list now require group membership - Simplify Casbin policy: remove g grouping, use r.sub == p.sub matcher with globMatch; user_roles table is single source of truth for roles - Add migration logic to clean legacy casbin g rules and migrate old p rules from path-style to admin/<domain> resource naming
276 lines
7.4 KiB
Go
276 lines
7.4 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
|
||
apperrors "with_you/internal/errors"
|
||
"with_you/internal/model"
|
||
"with_you/internal/repository"
|
||
)
|
||
|
||
// RoleService 角色管理服务接口
|
||
type RoleService interface {
|
||
// GetUserRoles 获取用户的所有角色
|
||
GetUserRoles(ctx context.Context, userID string) ([]model.Role, error)
|
||
|
||
// AssignRole 为用户分配角色
|
||
AssignRole(ctx context.Context, operatorID, targetUserID, role string) error
|
||
|
||
// RemoveRole 移除用户的角色
|
||
RemoveRole(ctx context.Context, operatorID, targetUserID, role string) error
|
||
|
||
// GetAllRoles 获取所有角色定义
|
||
GetAllRoles(ctx context.Context) ([]model.Role, error)
|
||
|
||
// GetRole 获取角色详情
|
||
GetRole(ctx context.Context, name string) (*model.Role, error)
|
||
|
||
// GetRoleDetail 获取角色详情(包含用户数量和权限)
|
||
GetRoleDetail(ctx context.Context, name string) (*RoleDetail, error)
|
||
|
||
// GetRolePermissions 获取角色的权限列表
|
||
GetRolePermissions(ctx context.Context, name string) ([]Permission, error)
|
||
|
||
// GetAllPermissions 获取系统中所有权限定义
|
||
GetAllPermissions(ctx context.Context) ([]Permission, error)
|
||
|
||
// UpdateRolePermissions 更新角色权限
|
||
UpdateRolePermissions(ctx context.Context, roleName string, permissions []Permission) error
|
||
}
|
||
|
||
// Permission 权限定义
|
||
type Permission struct {
|
||
Resource string `json:"resource"`
|
||
Action string `json:"action"`
|
||
}
|
||
|
||
// RoleDetail 角色详情
|
||
type RoleDetail struct {
|
||
Name string `json:"name"`
|
||
DisplayName string `json:"display_name"`
|
||
Description string `json:"description"`
|
||
ParentRole *string `json:"parent_role"`
|
||
Priority int `json:"priority"`
|
||
UserCount int64 `json:"user_count"`
|
||
Permissions []Permission `json:"permissions"`
|
||
CreatedAt string `json:"created_at"`
|
||
UpdatedAt string `json:"updated_at"`
|
||
}
|
||
|
||
type roleService struct {
|
||
roleRepo repository.RoleRepository
|
||
casbinSvc CasbinService
|
||
}
|
||
|
||
// NewRoleService 创建角色管理服务
|
||
func NewRoleService(
|
||
roleRepo repository.RoleRepository,
|
||
casbinSvc CasbinService,
|
||
) RoleService {
|
||
return &roleService{
|
||
roleRepo: roleRepo,
|
||
casbinSvc: casbinSvc,
|
||
}
|
||
}
|
||
|
||
func (s *roleService) GetUserRoles(ctx context.Context, userID string) ([]model.Role, error) {
|
||
roleNames, err := s.casbinSvc.GetRolesForUser(ctx, userID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var roles []model.Role
|
||
for _, name := range roleNames {
|
||
role, err := s.roleRepo.GetRole(ctx, name)
|
||
if err != nil {
|
||
continue // 忽略找不到的角色
|
||
}
|
||
roles = append(roles, *role)
|
||
}
|
||
return roles, nil
|
||
}
|
||
|
||
func (s *roleService) AssignRole(ctx context.Context, operatorID, targetUserID, role string) error {
|
||
// 不能修改自己的角色
|
||
if operatorID == targetUserID {
|
||
return apperrors.ErrCannotModifyOwnRole
|
||
}
|
||
|
||
// 纵深防御:分配 super_admin 仅 super_admin 自身可操作。
|
||
// 路由层 Authorize 已通过 admin.users.roles 限制 super_admin 能拿到此动作,
|
||
// 这里再独立校验 operator 角色,防止路由策略错配或服务被其他路径调用造成提权。
|
||
if role == model.RoleSuperAdmin {
|
||
roles, err := s.casbinSvc.GetRolesForUser(ctx, operatorID)
|
||
if err != nil {
|
||
return apperrors.ErrCasbinInternal
|
||
}
|
||
if !containsRole(roles, model.RoleSuperAdmin) {
|
||
return apperrors.ErrCannotModifySuperAdmin
|
||
}
|
||
}
|
||
|
||
// 检查目标用户是否已有该角色
|
||
hasRole, err := s.casbinSvc.HasRoleForUser(ctx, targetUserID, role)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if hasRole {
|
||
return apperrors.ErrRoleAlreadyAssigned
|
||
}
|
||
|
||
// 检查角色是否存在
|
||
_, err = s.roleRepo.GetRole(ctx, role)
|
||
if err != nil {
|
||
return apperrors.ErrRoleNotFound
|
||
}
|
||
|
||
// 添加角色
|
||
return s.casbinSvc.AddRoleForUser(ctx, targetUserID, role)
|
||
}
|
||
|
||
func (s *roleService) RemoveRole(ctx context.Context, operatorID, targetUserID, role string) error {
|
||
// 不能修改自己的角色
|
||
if operatorID == targetUserID {
|
||
return apperrors.ErrCannotModifyOwnRole
|
||
}
|
||
|
||
// 不能修改超级管理员角色
|
||
if role == model.RoleSuperAdmin {
|
||
return apperrors.ErrCannotModifySuperAdmin
|
||
}
|
||
|
||
// 纵深防御:移除其他用户的 super_admin 角色同样要求 operator 是 super_admin。
|
||
// (role==super_admin 已被上面拦截,这里针对未来扩展:如果允许移除其他高优先级角色再加规则。)
|
||
// 检查用户是否有该角色
|
||
hasRole, err := s.casbinSvc.HasRoleForUser(ctx, targetUserID, role)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if !hasRole {
|
||
return apperrors.ErrRoleNotAssigned
|
||
}
|
||
|
||
return s.casbinSvc.DeleteRoleForUser(ctx, targetUserID, role)
|
||
}
|
||
|
||
// containsRole 检查角色集合中是否包含指定角色。
|
||
func containsRole(roles []string, role string) bool {
|
||
for _, r := range roles {
|
||
if r == role {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (s *roleService) GetAllRoles(ctx context.Context) ([]model.Role, error) {
|
||
return s.roleRepo.GetAllRoles(ctx)
|
||
}
|
||
|
||
func (s *roleService) GetRole(ctx context.Context, name string) (*model.Role, error) {
|
||
return s.roleRepo.GetRole(ctx, name)
|
||
}
|
||
|
||
func (s *roleService) GetRoleDetail(ctx context.Context, name string) (*RoleDetail, error) {
|
||
role, err := s.roleRepo.GetRole(ctx, name)
|
||
if err != nil {
|
||
return nil, apperrors.ErrRoleNotFound
|
||
}
|
||
|
||
// 获取用户数量
|
||
userCount, err := s.roleRepo.GetRoleUserCount(ctx, name)
|
||
if err != nil {
|
||
userCount = 0
|
||
}
|
||
|
||
// 获取权限
|
||
permissions, err := s.casbinSvc.GetPermissionsForRole(ctx, name)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
perms := make([]Permission, 0)
|
||
for _, p := range permissions {
|
||
if len(p) >= 2 {
|
||
perms = append(perms, Permission{
|
||
Resource: p[0],
|
||
Action: p[1],
|
||
})
|
||
}
|
||
}
|
||
|
||
return &RoleDetail{
|
||
Name: role.Name,
|
||
DisplayName: role.DisplayName,
|
||
Description: role.Description,
|
||
ParentRole: role.ParentRole,
|
||
Priority: role.Priority,
|
||
UserCount: userCount,
|
||
Permissions: perms,
|
||
CreatedAt: role.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||
UpdatedAt: role.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||
}, nil
|
||
}
|
||
|
||
func (s *roleService) GetRolePermissions(ctx context.Context, name string) ([]Permission, error) {
|
||
// 检查角色是否存在
|
||
_, err := s.roleRepo.GetRole(ctx, name)
|
||
if err != nil {
|
||
return nil, apperrors.ErrRoleNotFound
|
||
}
|
||
|
||
permissions, err := s.casbinSvc.GetPermissionsForRole(ctx, name)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 初始化为空数组,避免 JSON 序列化为 null
|
||
perms := make([]Permission, 0)
|
||
for _, p := range permissions {
|
||
if len(p) >= 2 {
|
||
perms = append(perms, Permission{
|
||
Resource: p[0],
|
||
Action: p[1],
|
||
})
|
||
}
|
||
}
|
||
return perms, nil
|
||
}
|
||
|
||
func (s *roleService) GetAllPermissions(ctx context.Context) ([]Permission, error) {
|
||
permissions := s.casbinSvc.GetAllPermissions(ctx)
|
||
|
||
perms := make([]Permission, 0)
|
||
for _, p := range permissions {
|
||
if len(p) >= 3 {
|
||
// p[0] 是角色名,p[1] 是资源,p[2] 是操作
|
||
perms = append(perms, Permission{
|
||
Resource: p[1],
|
||
Action: p[2],
|
||
})
|
||
}
|
||
}
|
||
return perms, nil
|
||
}
|
||
|
||
func (s *roleService) UpdateRolePermissions(ctx context.Context, roleName string, permissions []Permission) error {
|
||
// 检查角色是否存在
|
||
_, err := s.roleRepo.GetRole(ctx, roleName)
|
||
if err != nil {
|
||
return apperrors.ErrRoleNotFound
|
||
}
|
||
|
||
// 不能修改超级管理员权限
|
||
if roleName == model.RoleSuperAdmin {
|
||
return apperrors.ErrCannotModifySuperAdmin
|
||
}
|
||
|
||
// 转换权限格式
|
||
var perms [][]string
|
||
for _, p := range permissions {
|
||
perms = append(perms, []string{p.Resource, p.Action})
|
||
}
|
||
|
||
return s.casbinSvc.UpdatePermissionsForRole(ctx, roleName, perms)
|
||
}
|