-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathmetadata_resolver.go
More file actions
342 lines (321 loc) · 10.4 KB
/
metadata_resolver.go
File metadata and controls
342 lines (321 loc) · 10.4 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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
package oidfed
import (
"encoding/json"
"github.com/google/go-querystring/query"
"github.com/pkg/errors"
"github.com/go-oidfed/lib/apimodel"
"github.com/go-oidfed/lib/internal"
"github.com/go-oidfed/lib/internal/http"
"github.com/go-oidfed/lib/internal/jwx"
"github.com/go-oidfed/lib/oidfedconst"
)
// MetadataResolver is type for resolving the metadata from a StartingEntity to
// one or multiple TrustAnchors
type MetadataResolver interface {
Resolve(request apimodel.ResolveRequest) (*Metadata, error)
ResolveResponsePayload(request apimodel.ResolveRequest) (ResolveResponsePayload, error)
ResolvePossible(request apimodel.ResolveRequest) (validConfirmed, invalidConfirmed bool)
}
// DefaultMetadataResolver is the default MetadataResolver used within the
// library to resolve Metadata
var DefaultMetadataResolver MetadataResolver = LocalMetadataResolver{}
// LocalMetadataResolver is a MetadataResolver that resolves trust chains and
// evaluates metadata policies to obtain the final Metadata; it does not use
// a resolve endpoint
type LocalMetadataResolver struct{}
// Resolve implements the MetadataResolver interface
func (r LocalMetadataResolver) Resolve(req apimodel.ResolveRequest) (*Metadata, error) {
res, _, err := r.resolveResponsePayloadWithoutTrustMarks(req)
if err != nil {
return nil, err
}
return res.Metadata, nil
}
func (LocalMetadataResolver) resolveResponsePayloadWithoutTrustMarks(
req apimodel.ResolveRequest,
) (
res ResolveResponsePayload, chain TrustChain, err error,
) {
tr := TrustResolver{
TrustAnchors: NewTrustAnchorsFromEntityIDs(req.TrustAnchor...),
StartingEntity: req.Subject,
Types: req.EntityTypes,
TrustAnchorHintsMode: TrustAnchorHintsModePrefer,
}
chains := tr.ResolveToValidChains()
if len(chains) == 0 {
err = errors.New("no trust chain found")
return
}
chains = chains.SortAsc(TrustChainScoringPathLen)
for _, chain = range chains {
m, err := chain.Metadata()
if err == nil {
res.TrustChain = chain.Messages()
res.Metadata = m
res.TrustAnchor = chain[len(chain)-1].Issuer
return res, chain, nil
}
}
err = errors.New("no trust chain with valid metadata found")
return
}
// ResolveResponsePayload implements the MetadataResolver interface
func (r LocalMetadataResolver) ResolveResponsePayload(req apimodel.ResolveRequest) (
res ResolveResponsePayload, err error,
) {
var chain TrustChain
res, chain, err = r.resolveResponsePayloadWithoutTrustMarks(req)
if err != nil {
return
}
if len(chain) == 0 {
return res, errors.New("no trust chain returned")
}
res.TrustMarks = chain[0].TrustMarks.VerifiedFederation(&chain[len(chain)-1].EntityStatementPayload)
return
}
// ResolvePossible implements the MetadataResolver interface
func (LocalMetadataResolver) ResolvePossible(req apimodel.ResolveRequest) (bool, bool) {
tr := TrustResolver{
TrustAnchors: NewTrustAnchorsFromEntityIDs(req.TrustAnchor...),
StartingEntity: req.Subject,
Types: req.EntityTypes,
TrustAnchorHintsMode: TrustAnchorHintsModeIgnore,
}
chains := tr.ResolveToValidChains()
valid := len(chains) > 0
return valid, !valid
}
// SimpleRemoteMetadataResolver is a MetadataResolver that utilizes a given
// ResolveEndpoint
type SimpleRemoteMetadataResolver struct {
ResolveEndpoint string
}
const (
resolveStatusUnknown = iota
resolveStatusValid
resolveStatusOnlyValidTrustChain
resolveStatusInvalid
resolveStatusNotAcceptable
)
// ResolveResponse returns the ResolveResponse from a response endpoint
func (r SimpleRemoteMetadataResolver) ResolveResponse(req apimodel.ResolveRequest) (
*ResolveResponse, int, error,
) {
// Default to unknown until we positively parse a valid response
var resolveStatus = resolveStatusUnknown
params, err := query.Values(req)
if err != nil {
return nil, resolveStatus, errors.WithStack(err)
}
res, errRes, err := http.Get(r.ResolveEndpoint, params, nil)
if err != nil {
return nil, resolveStatus, err
}
if errRes != nil {
switch errRes.Error {
case InvalidSubject, InvalidTrustAnchor:
resolveStatus = resolveStatusNotAcceptable
case InvalidTrustChain:
resolveStatus = resolveStatusInvalid
case InvalidMetadata:
resolveStatus = resolveStatusOnlyValidTrustChain
default:
resolveStatus = resolveStatusUnknown
}
return nil, resolveStatus, nil
}
rres, err := ParseResolveResponse(res.Body())
if err != nil {
// Keep status at unknown for parse/format errors
return nil, resolveStatus, err
}
resolveStatus = resolveStatusValid
return rres, resolveStatus, nil
}
// Resolve implements the MetadataResolver interface
func (r SimpleRemoteMetadataResolver) Resolve(req apimodel.ResolveRequest) (*Metadata, error) {
res, resStatus, err := r.ResolveResponse(req)
if err != nil {
return nil, err
}
if resStatus != resolveStatusValid {
return nil, errors.New("no positive resolve response from remote resolver")
}
return res.Metadata, nil
}
// ResolveResponsePayload implements the MetadataResolver interface
func (r SimpleRemoteMetadataResolver) ResolveResponsePayload(req apimodel.ResolveRequest) (
ResolveResponsePayload, error,
) {
res, resStatus, err := r.ResolveResponse(req)
if err != nil {
return ResolveResponsePayload{}, err
}
if resStatus != resolveStatusValid {
return ResolveResponsePayload{}, errors.New("no positive resolve response from remote resolver")
}
return res.ResolveResponsePayload, nil
}
// ResolvePossible implements the MetadataResolver interface
func (r SimpleRemoteMetadataResolver) ResolvePossible(req apimodel.ResolveRequest) (bool, bool) {
_, resStatus, err := r.ResolveResponse(req)
if err != nil {
internal.Log(err.Error())
return false, true
}
switch resStatus {
case resolveStatusValid, resolveStatusOnlyValidTrustChain:
return true, false
case resolveStatusInvalid:
return false, true
default:
return false, false
}
}
// ParseResolveResponse parses a jwt into a ResolveResponse
func ParseResolveResponse(body []byte) (*ResolveResponse, error) {
r, err := jwx.Parse(body)
if err != nil {
return nil, err
}
if !r.VerifyType(oidfedconst.JWTTypeResolveResponse) {
return nil, errors.Errorf("response does not have '%s' JWT type", oidfedconst.JWTTypeResolveResponse)
}
var res ResolveResponse
payload := r.Payload()
// Guard against null payloads which would cause a nil deref when used by callers
if string(payload) == "null" {
return nil, errors.New("invalid resolve response: null payload")
}
if err = json.Unmarshal(payload, &res); err != nil {
return nil, err
}
return &res, err
}
// SmartRemoteMetadataResolver is a MetadataResolver that utilizes remote
// resolve endpoints. It will iterate through the resolve endpoints of the
// given TrustAnchors and stop if one is successful,
// if no resolve endpoint is successful, local resolving is used
type SmartRemoteMetadataResolver struct{}
// Resolve implements the MetadataResolver interface
func (r SmartRemoteMetadataResolver) Resolve(req apimodel.ResolveRequest) (*Metadata, error) {
res, err := r.ResolveResponsePayload(req)
if err != nil {
return nil, err
}
return res.Metadata, nil
}
// ResolveResponsePayload implements the MetadataResolver interface
func (SmartRemoteMetadataResolver) ResolveResponsePayload(req apimodel.ResolveRequest) (
ResolveResponsePayload, error,
) {
// Prefer trust anchors hinted by the starting entity; fall back to others.
// Default mode: Prefer
var ordered []string
if req.Subject != "" {
if starting, err := GetEntityConfiguration(req.Subject); err == nil && starting != nil {
hints := starting.TrustAnchorHints
if len(hints) > 0 {
hintSet := map[string]struct{}{}
for _, h := range hints {
hintSet[h] = struct{}{}
}
// intersection first, preserving req.TrustAnchor order
for _, id := range req.TrustAnchor {
if _, ok := hintSet[id]; ok {
ordered = append(ordered, id)
}
}
// then the remaining anchors not in hints
for _, id := range req.TrustAnchor {
if _, ok := hintSet[id]; !ok {
ordered = append(ordered, id)
}
}
}
}
}
if len(ordered) == 0 {
ordered = req.TrustAnchor
}
for _, tr := range ordered {
entityConfig, err := GetEntityConfiguration(tr)
if err != nil {
internal.Logf("MetadataResolver: error while obtaining entity configuration: %v", err)
continue
}
var resolveEndpoint string
if entityConfig != nil && entityConfig.Metadata != nil && entityConfig.Metadata.FederationEntity != nil {
resolveEndpoint = entityConfig.Metadata.FederationEntity.FederationResolveEndpoint
}
if resolveEndpoint == "" {
continue
}
remoteResolver := SimpleRemoteMetadataResolver{
ResolveEndpoint: resolveEndpoint,
}
res, err := remoteResolver.ResolveResponsePayload(req)
if err != nil {
internal.Logf("MetadataResolver: error while obtaining resolve response: %v", err)
continue
}
return res, nil
}
return LocalMetadataResolver{}.ResolveResponsePayload(req)
}
// ResolvePossible implements the MetadataResolver interface
func (SmartRemoteMetadataResolver) ResolvePossible(req apimodel.ResolveRequest) (bool, bool) {
// Prefer trust anchors hinted by the starting entity; fall back to others.
var ordered []string
if req.Subject != "" {
if starting, err := GetEntityConfiguration(req.Subject); err == nil && starting != nil {
hints := starting.TrustAnchorHints
if len(hints) > 0 {
hintSet := map[string]struct{}{}
for _, h := range hints {
hintSet[h] = struct{}{}
}
for _, id := range req.TrustAnchor {
if _, ok := hintSet[id]; ok {
ordered = append(ordered, id)
}
}
for _, id := range req.TrustAnchor {
if _, ok := hintSet[id]; !ok {
ordered = append(ordered, id)
}
}
}
}
}
if len(ordered) == 0 {
ordered = req.TrustAnchor
}
for _, tr := range ordered {
entityConfig, err := GetEntityConfiguration(tr)
if err != nil {
internal.Logf("MetadataResolver: error while obtaining entity configuration: %v", err)
continue
}
var resolveEndpoint string
if entityConfig != nil && entityConfig.Metadata != nil && entityConfig.Metadata.FederationEntity != nil {
resolveEndpoint = entityConfig.Metadata.FederationEntity.FederationResolveEndpoint
}
if resolveEndpoint == "" {
continue
}
remoteResolver := SimpleRemoteMetadataResolver{
ResolveEndpoint: resolveEndpoint,
}
validConfirmed, invalidConfirmed := remoteResolver.ResolvePossible(req)
if validConfirmed {
return true, false
}
if invalidConfirmed {
return false, true
}
}
return LocalMetadataResolver{}.ResolvePossible(req)
}