Smail/backend/services/access/authorize.go
smail fd475cd88d
Some checks are pending
Security Checks / security (push) Waiting to run
保存角色权限分层改造续审现场
2026-09-09 20:27:50 +08:00

126 lines
3.7 KiB
Go

package access
import "errors"
type DenialCode string
const (
CodeAccessDenied DenialCode = "ACCESS_DENIED"
CodeInvalidRole DenialCode = "INVALID_ROLE"
CodeUnknownCapability DenialCode = "UNKNOWN_CAPABILITY"
CodeRecentAuthRequired DenialCode = "RECENT_AUTH_REQUIRED"
CodeRecoveryCodeRequired DenialCode = "RECOVERY_CODE_REQUIRED"
CodeTargetRequired DenialCode = "TARGET_REQUIRED"
CodeTargetRoleDenied DenialCode = "TARGET_ROLE_DENIED"
CodePluginScopeDenied DenialCode = "PLUGIN_SCOPE_DENIED"
)
type Denial struct{ Code DenialCode }
func (e *Denial) Error() string { return string(e.Code) }
func IsDenied(err error, code DenialCode) bool {
var denial *Denial
return errors.As(err, &denial) && denial.Code == code
}
type Request struct {
ActorRole Role
Capability Capability
TargetRole Role
NewRole Role
OwnResource bool
RecentAuth bool
RecoveryCodeConfigured bool
TargetRecoveryConfigured bool
PluginInstanceAuthorized bool
AuditScopeAuthorized bool
DeploymentChannel bool
}
func Authorize(request Request) error {
if !request.ActorRole.Valid() {
return deny(CodeInvalidRole)
}
policy, ok := GetPolicy(request.Capability)
if !ok {
return deny(CodeUnknownCapability)
}
if _, ok := grants[request.ActorRole][request.Capability]; !ok {
return deny(CodeAccessDenied)
}
if policy.RequiresTarget && !request.TargetRole.Valid() {
return deny(CodeTargetRequired)
}
if policy.RequiresRecentAuth && !request.RecentAuth {
return deny(CodeRecentAuthRequired)
}
if policy.RequiresRecoveryCode && request.ActorRole != RoleSuperAdmin && !request.RecoveryCodeConfigured && !request.DeploymentChannel {
return deny(CodeRecoveryCodeRequired)
}
return authorizeResource(request)
}
func authorizeResource(request Request) error {
switch request.Capability {
case CapabilityPasswordChangeOwn, CapabilityPluginWriteOwn, CapabilityProfileWriteOwn:
if !request.OwnResource {
return deny(CodeAccessDenied)
}
case CapabilityUserRead:
if request.ActorRole == RoleUser && !request.OwnResource {
return deny(CodeAccessDenied)
}
case CapabilityAuditRead:
if request.ActorRole == RoleUser && !request.OwnResource {
return deny(CodeAccessDenied)
}
if request.ActorRole == RoleAdmin && !request.OwnResource && !request.AuditScopeAuthorized {
return deny(CodeAccessDenied)
}
case CapabilityUserCreate, CapabilityUserManage, CapabilityUserSecurityManage, CapabilityPasswordReset:
if !canManageAccountTarget(request.ActorRole, request.TargetRole) {
return deny(CodeTargetRoleDenied)
}
case CapabilityPluginWriteAnyUser:
if !canManagePluginTarget(request.ActorRole, request.TargetRole) {
return deny(CodeTargetRoleDenied)
}
case CapabilityRoleManage:
if !canManageAccountTarget(request.ActorRole, request.TargetRole) || !request.NewRole.Valid() || request.NewRole == RoleSuperAdmin {
return deny(CodeTargetRoleDenied)
}
if !request.TargetRecoveryConfigured {
return deny(CodeRecoveryCodeRequired)
}
case CapabilityPluginRestart:
if request.ActorRole == RoleAdmin && !request.PluginInstanceAuthorized {
return deny(CodePluginScopeDenied)
}
}
return nil
}
func canManageAccountTarget(actor Role, target Role) bool {
switch actor {
case RoleSuperAdmin:
return target == RoleAdmin || target == RoleUser
case RoleAdmin:
return target == RoleUser
default:
return false
}
}
func canManagePluginTarget(actor Role, target Role) bool {
if actor == RoleSuperAdmin {
return target.Valid()
}
if actor == RoleAdmin {
return target == RoleUser
}
return false
}
func deny(code DenialCode) error { return &Denial{Code: code} }