/
declared.go
137 lines (125 loc) · 4.14 KB
/
declared.go
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
package checkers
import (
"context"
"strings"
"gopkg.in/errgo.v1"
"gopkg.in/macaroon.v2"
)
type macaroonsKey struct{}
type macaroonsValue struct {
ns *Namespace
ms macaroon.Slice
}
// ContextWithMacaroons returns the given context associated with a
// macaroon slice and the name space to use to interpret caveats in
// the macaroons.
func ContextWithMacaroons(ctx context.Context, ns *Namespace, ms macaroon.Slice) context.Context {
return context.WithValue(ctx, macaroonsKey{}, macaroonsValue{
ns: ns,
ms: ms,
})
}
// MacaroonsFromContext returns the namespace and macaroons associated
// with the context by ContextWithMacaroons. This can be used to
// implement "structural" first-party caveats that are predicated on
// the macaroons being validated.
func MacaroonsFromContext(ctx context.Context) (*Namespace, macaroon.Slice) {
v, _ := ctx.Value(macaroonsKey{}).(macaroonsValue)
return v.ns, v.ms
}
// DeclaredCaveat returns a "declared" caveat asserting that the given key is
// set to the given value. If a macaroon has exactly one first party
// caveat asserting the value of a particular key, then InferDeclared
// will be able to infer the value, and then DeclaredChecker will allow
// the declared value if it has the value specified here.
//
// If the key is empty or contains a space, DeclaredCaveat
// will return an error caveat.
func DeclaredCaveat(key string, value string) Caveat {
if strings.Contains(key, " ") || key == "" {
return ErrorCaveatf("invalid caveat 'declared' key %q", key)
}
return firstParty(CondDeclared, key+" "+value)
}
// NeedDeclaredCaveat returns a third party caveat that
// wraps the provided third party caveat and requires
// that the third party must add "declared" caveats for
// all the named keys.
// TODO(rog) namespaces in third party caveats?
func NeedDeclaredCaveat(cav Caveat, keys ...string) Caveat {
if cav.Location == "" {
return ErrorCaveatf("need-declared caveat is not third-party")
}
return Caveat{
Location: cav.Location,
Condition: CondNeedDeclared + " " + strings.Join(keys, ",") + " " + cav.Condition,
}
}
func checkDeclared(ctx context.Context, _, arg string) error {
parts := strings.SplitN(arg, " ", 2)
if len(parts) != 2 {
return errgo.Newf("declared caveat has no value")
}
ns, ms := MacaroonsFromContext(ctx)
attrs := InferDeclared(ns, ms)
val, ok := attrs[parts[0]]
if !ok {
return errgo.Newf("got %s=null, expected %q", parts[0], parts[1])
}
if val != parts[1] {
return errgo.Newf("got %s=%q, expected %q", parts[0], val, parts[1])
}
return nil
}
// InferDeclared retrieves any declared information from
// the given macaroons and returns it as a key-value map.
//
// Information is declared with a first party caveat as created
// by DeclaredCaveat.
//
// If there are two caveats that declare the same key with
// different values, the information is omitted from the map.
// When the caveats are later checked, this will cause the
// check to fail.
func InferDeclared(ns *Namespace, ms macaroon.Slice) map[string]string {
var conditions []string
for _, m := range ms {
for _, cav := range m.Caveats() {
if cav.Location == "" {
conditions = append(conditions, string(cav.Id))
}
}
}
return InferDeclaredFromConditions(ns, conditions)
}
// InferDeclaredFromConditions is like InferDeclared except that
// it is passed a set of first party caveat conditions rather than a set of macaroons.
func InferDeclaredFromConditions(ns *Namespace, conds []string) map[string]string {
var conflicts []string
// If we can't resolve that standard namespace, then we'll look for
// just bare "declared" caveats which will work OK for legacy
// macaroons with no namespace.
prefix, _ := ns.Resolve(StdNamespace)
declaredCond := prefix + CondDeclared
info := make(map[string]string)
for _, cond := range conds {
name, rest, _ := ParseCaveat(cond)
if name != declaredCond {
continue
}
parts := strings.SplitN(rest, " ", 2)
if len(parts) != 2 {
continue
}
key, val := parts[0], parts[1]
if oldVal, ok := info[key]; ok && oldVal != val {
conflicts = append(conflicts, key)
continue
}
info[key] = val
}
for _, key := range conflicts {
delete(info, key)
}
return info
}