Repository navigation
Expand file tree
/
Copy pathreach.go
More file actions
91 lines (78 loc) · 2.93 KB
/
Copy pathreach.go
File metadata and controls
91 lines (78 loc) · 2.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// SPDX-License-Identifier: Apache-2.0
package auth
import (
"context"
"fmt"
"slices"
"github.com/gopherium/gouncer"
"github.com/gopherium/framework/gonsole"
)
// reach is the acting account of one call and what each role carries, the zero value refusing nothing.
type reach struct {
actor gouncer.User
carried map[string][]string
}
// withReach runs use over the account store and the reach of the account the call acts as.
func withReach(ctx context.Context, call gonsole.Call, cfg Config, roles Roles,
use func(store Accounts, within reach) error) error {
if cfg.Capability == "" {
return cfg.withStore(ctx, call, func(store Accounts) error {
return use(store, reach{})
})
}
return cfg.withStore(ctx, call, func(store Accounts) error {
user, err := acting(ctx, store, address(call.Actor))
if err != nil {
return err
}
return use(store, reach{actor: user, carried: roles.Capabilities})
})
}
// gives refuses a role that carries a capability the acting account's role lacks.
func (r reach) gives(role string) error {
return r.lacking("the role "+role, role)
}
// changes refuses an account whose role carries a capability the acting account's role lacks.
func (r reach) changes(held gouncer.User) error {
return r.lacking(fmt.Sprintf("the role %s of %s", held.Role, held.Email), held.Role)
}
// moves refuses a change to an account beyond its reach and the acting account changing its own role.
func (r reach) moves(held gouncer.User) error {
if err := r.changes(held); err != nil {
return err
}
if held.ID == r.actor.ID {
return fmt.Errorf("the account %s cannot change its own role", held.Email)
}
return nil
}
// sets refuses the acting account disabling itself and a change to an account beyond its reach.
func (r reach) sets(held gouncer.User, disabled bool) error {
if disabled && held.ID == r.actor.ID {
return fmt.Errorf("the account %s cannot disable itself", held.Email)
}
return r.changes(held)
}
// reenabling is what an account holding an undeclared role awaits before it may be enabled.
const reenabling = "give it a declared role before enabling it"
// lost refuses enabling an account whose stored role roles does not declare, an empty role included.
func lost(roles Roles, held gouncer.User, disabled bool) error {
switch {
case disabled:
return nil
case held.Role == "":
return fmt.Errorf("the account %s holds no role, %s", held.Email, reenabling)
case !slices.Contains(roles.Known, held.Role):
return fmt.Errorf("the account %s holds the undeclared role %s, %s", held.Email, held.Role, reenabling)
}
return nil
}
// lacking returns the error naming the first capability of role the acting account's role lacks, nil for none.
func (r reach) lacking(subject, role string) error {
for _, capability := range r.carried[role] {
if !slices.Contains(r.carried[r.actor.Role], capability) {
return fmt.Errorf("%s carries %s, which the account %s lacks", subject, capability, r.actor.Email)
}
}
return nil
}