forked from lightninglabs/aperture
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsatisfier.go
More file actions
180 lines (161 loc) · 5.38 KB
/
satisfier.go
File metadata and controls
180 lines (161 loc) · 5.38 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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package lsat
import (
"fmt"
"strconv"
"strings"
"time"
)
// Satisfier provides a generic interface to satisfy a caveat based on its
// condition.
type Satisfier struct {
// Condition is the condition of the caveat we'll attempt to satisfy.
Condition string
// SatisfyPrevious ensures a caveat is in accordance with a previous one
// with the same condition. This is needed since caveats of the same
// condition can be used multiple times as long as they enforce more
// permissions than the previous.
//
// For example, we have a caveat that only allows us to use an LSAT for
// 7 more days. We can add another caveat that only allows for 3 more
// days of use and lend it to another party.
SatisfyPrevious func(previous Caveat, current Caveat) error
// SatisfyFinal satisfies the final caveat of an LSAT. If multiple
// caveats with the same condition exist, this will only be executed
// once all previous caveats are also satisfied.
SatisfyFinal func(Caveat) error
}
// NewServicesSatisfier implements a satisfier to determine whether the target
// service is authorized for a given LSAT.
//
// TODO(wilmer): Add tier verification?
func NewServicesSatisfier(targetService string) Satisfier {
return Satisfier{
Condition: CondServices,
SatisfyPrevious: func(prev, cur Caveat) error {
// Construct a set of the services we were previously
// allowed to access.
prevServices, err := decodeServicesCaveatValue(prev.Value)
if err != nil {
return err
}
prevAllowed := make(map[string]struct{}, len(prevServices))
for _, service := range prevServices {
prevAllowed[service.Name] = struct{}{}
}
// The caveat should not include any new services that
// weren't previously allowed.
currentServices, err := decodeServicesCaveatValue(cur.Value)
if err != nil {
return err
}
for _, service := range currentServices {
if _, ok := prevAllowed[service.Name]; !ok {
return fmt.Errorf("service %v not "+
"previously allowed", service)
}
}
return nil
},
SatisfyFinal: func(c Caveat) error {
services, err := decodeServicesCaveatValue(c.Value)
if err != nil {
return err
}
for _, service := range services {
if service.Name == targetService {
return nil
}
}
return fmt.Errorf("target service %v not authorized",
targetService)
},
}
}
// NewCapabilitiesSatisfier implements a satisfier to determine whether the
// target capability for a service is authorized for a given LSAT.
func NewCapabilitiesSatisfier(service string,
targetCapability string) Satisfier {
return Satisfier{
Condition: service + CondCapabilitiesSuffix,
SatisfyPrevious: func(prev, cur Caveat) error {
// Construct a set of the service's capabilities we were
// previously allowed to access.
prevCapabilities := strings.Split(prev.Value, ",")
allowed := make(map[string]struct{}, len(prevCapabilities))
for _, capability := range prevCapabilities {
allowed[capability] = struct{}{}
}
// The caveat should not include any new service
// capabilities that weren't previously allowed.
currentCapabilities := strings.Split(cur.Value, ",")
for _, capability := range currentCapabilities {
if _, ok := allowed[capability]; !ok {
return fmt.Errorf("capability %v not "+
"previously allowed", capability)
}
}
return nil
},
SatisfyFinal: func(c Caveat) error {
capabilities := strings.Split(c.Value, ",")
for _, capability := range capabilities {
if capability == targetCapability {
return nil
}
}
return fmt.Errorf("target capability %v not authorized",
targetCapability)
},
}
}
// NewTimeoutSatisfier checks if an LSAT is expired or not. The Satisfier takes
// a service name to set as the condition prefix and currentTimestamp to
// compare against the expiration(s) in the caveats. The expiration time is
// retrieved from the caveat values themselves. The satisfier will also make
// sure that each subsequent caveat of the same condition only has increasingly
// strict expirations.
func NewTimeoutSatisfier(service string, now func() time.Time) Satisfier {
return Satisfier{
Condition: service + CondTimeoutSuffix,
SatisfyPrevious: func(prev, cur Caveat) error {
prevValue, err := strconv.ParseInt(prev.Value, 10, 64)
if err != nil {
return fmt.Errorf("error parsing previous "+
"caveat value: %w", err)
}
currValue, err := strconv.ParseInt(cur.Value, 10, 64)
if err != nil {
return fmt.Errorf("error parsing caveat "+
"value: %w", err)
}
prevTime := time.Unix(prevValue, 0)
currTime := time.Unix(currValue, 0)
// Satisfier should fail if a previous timestamp in the
// list is earlier than ones after it b/c that means
// they are getting more permissive.
if prevTime.Before(currTime) {
return fmt.Errorf("%s caveat violates "+
"increasing restrictiveness",
service+CondTimeoutSuffix)
}
return nil
},
SatisfyFinal: func(c Caveat) error {
expirationTimestamp, err := strconv.ParseInt(
c.Value, 10, 64,
)
if err != nil {
return fmt.Errorf("caveat value not a valid "+
"integer: %v", err)
}
expirationTime := time.Unix(expirationTimestamp, 0)
// Make sure that the final relevant caveat is not
// passed the current date/time.
if now().Before(expirationTime) {
return nil
}
return fmt.Errorf("not authorized to access " +
"service. LSAT has expired")
},
}
}