permission.go•3.57 kB
package rbac
import (
"context"
"fmt"
"strings"
"github.com/Southclaws/dt"
"github.com/Southclaws/fault"
"github.com/Southclaws/fault/fctx"
"github.com/Southclaws/fault/ftag"
)
var ErrPermissions = fault.New("invalid permissions", ftag.With(ftag.PermissionDenied))
var readPermissions = []Permission{
PermissionReadPublishedThreads,
PermissionReadPublishedLibrary,
PermissionListProfiles,
PermissionReadProfile,
PermissionListCollections,
PermissionReadCollection,
}
var writePermissions = []Permission{
PermissionCreatePost,
PermissionCreateReaction,
PermissionManagePosts,
PermissionManageCategories,
PermissionCreateInvitation,
PermissionManageLibrary,
PermissionSubmitLibraryNode,
PermissionUploadAsset,
PermissionManageEvents,
PermissionCreateCollection,
PermissionManageCollections,
PermissionCollectionSubmit,
PermissionUsePersonalAccessKeys,
PermissionManageSettings,
PermissionManageSuspensions,
PermissionManageRoles,
PermissionViewAccounts,
PermissionAdministrator,
}
// Type Permission is generated by rbacgen, the source of truth is openapi.yaml.
type PermissionList []Permission
func (p PermissionList) String() string {
s := make([]string, len(p))
for i, pp := range p {
s[i] = pp.String()
}
return strings.Join(s, ",")
}
type Permissions struct {
p PermissionList
m map[Permission]struct{}
}
func NewPermissions(s []string) (*Permissions, error) {
ps, err := dt.MapErr(s, NewPermission)
if err != nil {
return nil, err
}
list := NewList(ps...)
return &list, nil
}
func (p Permissions) List() PermissionList {
return p.p
}
func NewList(permissions ...Permission) Permissions {
m := map[Permission]struct{}{}
for _, p := range permissions {
m[p] = struct{}{}
}
return Permissions{permissions, m}
}
func (p Permissions) HasAll(perms ...Permission) bool {
for _, pp := range perms {
if _, ok := p.m[pp]; !ok {
return false
}
}
return true
}
func (p Permissions) HasAny(perms ...Permission) bool {
for _, pp := range perms {
if _, ok := p.m[pp]; ok {
return true
}
}
return false
}
func (p Permissions) HasAnyWrite() bool {
for _, pp := range writePermissions {
if _, ok := p.m[pp]; ok {
return true
}
}
return false
}
func (p Permissions) HasAnyRead() bool {
for _, pp := range readPermissions {
if _, ok := p.m[pp]; ok {
return true
}
}
return false
}
// Authorise will check if the account holds any of the permissions provided. If
// it does not, it then runs the additional check function in order to apply any
// domain-specific logic such as resource ownership, to determine authorisation.
func (p Permissions) Authorise(ctx context.Context, fn func() error, perms ...Permission) error {
ctx = fctx.WithMeta(ctx,
"permissions", PermissionList(perms).String(),
)
// PermissionAdministrator can do anything.
if p.HasAny(PermissionAdministrator) {
return nil
}
// If permissions are valid, do not need to run additional check function.
if p.HasAny(perms...) {
return nil
}
// Additional check functions are for when permission requires logic, such
// as checking for resource ownership or non-publishing draft submissions.
if fn != nil {
if err := fn(); err != nil {
return fault.Wrap(
fmt.Errorf("%w: additional check failed: %w", ErrPermissions, err),
fctx.With(ctx),
ftag.With(ftag.PermissionDenied),
)
}
// If the additional check function passes, override the missing perm.
return nil
}
// No additional check and permission is missing.
return fault.Wrap(ErrPermissions, fctx.With(ctx))
}