-
Notifications
You must be signed in to change notification settings - Fork 299
/
request.go
442 lines (353 loc) · 11.2 KB
/
request.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
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
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
package kite
import (
"context"
"errors"
"fmt"
"runtime/debug"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/koding/cache"
"github.com/koding/kite/dnode"
"github.com/koding/kite/kitekey"
"github.com/koding/kite/protocol"
"github.com/koding/kite/sockjsclient"
"github.com/koding/kite/utils"
)
// Request contains information about the incoming request.
type Request struct {
// ID is an unique string, which may be used for tracing the request.
ID string
// Method defines the method name which is invoked by the incoming request.
Method string
// Username defines the username which the incoming request is bound to.
// This is authenticated and validated if authentication is enabled.
Username string
// Args defines the incoming arguments for the given method.
Args *dnode.Partial
// LocalKite defines a context for the local kite.
LocalKite *Kite
// Client defines a context for the remote kite.
Client *Client
// Auth stores the authentication information for the incoming request and
// the type of authentication. This is not used when authentication is disabled.
Auth *Auth
// Context holds a context that used by the current ServeKite handler. Any
// items added to the Context can be fetched from other handlers in the
// chain. This is useful with PreHandle and PostHandle handlers to pass
// data between handlers.
//
// The context is canceled when client has disconnected or session
// was prematurely terminated.
Context context.Context
}
// Response is the type of the object that is returned from request handlers
// and the type of only argument that is passed to callback functions.
type Response struct {
Error *Error `json:"error" dnode:"-"`
Result interface{} `json:"result"`
}
// runMethod is called when a method is received from remote Kite.
func (c *Client) runMethod(method *Method, args *dnode.Partial) {
var (
callFunc func(interface{}, *Error)
request *Request
)
// Recover dnode argument errors and send them back. The caller can use
// functions like MustString(), MustSlice()... without the fear of panic.
defer func() {
if r := recover(); r != nil {
debug.PrintStack()
kiteErr := createError(request, r)
c.LocalKite.Log.Error(kiteErr.Error()) // let's log it too :)
callFunc(nil, kiteErr)
}
}()
// The request that will be constructed from incoming dnode message.
request, callFunc = c.newRequest(method.name, args)
if method.authenticate {
if err := request.authenticate(); err != nil {
callFunc(nil, createError(request, err))
return
}
} else {
// if not validated accept any username it sends, also useful for test
// cases.
request.Username = request.Client.Kite.Username
}
method.mu.Lock()
if !method.initialized {
method.preHandlers = append(method.preHandlers, c.LocalKite.preHandlers...)
method.postHandlers = append(method.postHandlers, c.LocalKite.postHandlers...)
method.finalFuncs = append(method.finalFuncs, c.LocalKite.finalFuncs...)
method.initialized = true
}
method.mu.Unlock()
// check if any throttling is enabled and then check token's available.
// Tokens are filled per frequency of the initial bucket, so every request
// is going to take one token from the bucket. If many requests come in (in
// span time larger than the bucket's frequency), there will be no token's
// available more so it will return a zero.
if method.bucket != nil && method.bucket.TakeAvailable(1) == 0 {
callFunc(nil, &Error{
Type: "requestLimitError",
Message: "The maximum request rate is exceeded.",
RequestID: request.ID,
})
return
}
// Call the handler functions.
result, err := method.ServeKite(request)
callFunc(result, createError(request, err))
}
// runCallback is called when a callback method call is received from remote Kite.
func (c *Client) runCallback(callback func(*dnode.Partial), args *dnode.Partial) {
// Do not panic no matter what.
defer func() {
if err := recover(); err != nil {
c.LocalKite.Log.Warning("Error in calling the callback function : %v", err)
}
}()
// Call the callback function.
callback(args)
}
// newRequest returns a new *Request from the method and arguments passed.
func (c *Client) newRequest(method string, args *dnode.Partial) (*Request, func(interface{}, *Error)) {
// Parse dnode method arguments: [options]
var options callOptions
args.One().MustUnmarshal(&options)
// Notify the handlers registered with Kite.OnFirstRequest().
if _, ok := c.session.(*sockjsclient.WebsocketSession); !ok {
c.firstRequestHandlersNotified.Do(func() {
c.m.Lock()
c.Kite = options.Kite
c.m.Unlock()
c.LocalKite.callOnFirstRequestHandlers(c)
})
}
request := &Request{
ID: utils.RandomString(16),
Method: method,
Args: options.WithArgs,
LocalKite: c.LocalKite,
Client: c,
Auth: options.Auth,
Context: c.context(),
}
// Call response callback function, send back our response
callFunc := func(result interface{}, err *Error) {
if options.ResponseCallback.Caller == nil {
return
}
// Only argument to the callback.
response := Response{
Result: result,
Error: err,
}
if err := options.ResponseCallback.Call(response); err != nil {
c.LocalKite.Log.Error(err.Error())
}
}
return request, callFunc
}
// authenticate tries to authenticate the user by selecting appropriate
// authenticator function.
func (r *Request) authenticate() *Error {
// Trust the Kite if we have initiated the connection. Following casts
// means, session is opened by the client.
if _, ok := r.Client.session.(*sockjsclient.WebsocketSession); ok {
return nil
}
if _, ok := r.Client.session.(*sockjsclient.XHRSession); ok {
return nil
}
if r.Auth == nil {
return &Error{
Type: "authenticationError",
Message: "No authentication information is provided",
}
}
// Select authenticator function.
f := r.LocalKite.Authenticators[r.Auth.Type]
if f == nil {
return &Error{
Type: "authenticationError",
Message: fmt.Sprintf("Unknown authentication type: %s", r.Auth.Type),
}
}
// Call authenticator function. It sets the Request.Username field.
err := f(r)
if err != nil {
return &Error{
Type: "authenticationError",
Message: fmt.Sprintf("%s: %s", r.Auth.Type, err),
}
}
// Replace username of the remote Kite with the username that client send
// us. This prevents a Kite to impersonate someone else's Kite.
r.Client.SetUsername(r.Username)
return nil
}
// AuthenticateFromToken is the default Authenticator for Kite.
func (k *Kite) AuthenticateFromToken(r *Request) error {
k.verifyOnce.Do(k.verifyInit)
token, err := jwt.ParseWithClaims(r.Auth.Key, &kitekey.KiteClaims{}, r.LocalKite.RSAKey)
if e, ok := err.(*jwt.ValidationError); ok {
// Translate public key mismatch errors to token-is-expired one.
// This is to signal remote client the key pairs have been
// updated on kontrol and it should invalidate all tokens.
if (e.Errors & jwt.ValidationErrorSignatureInvalid) != 0 {
return errors.New("token is expired")
}
}
if err != nil {
return err
}
if !token.Valid {
return errors.New("Invalid signature in token")
}
claims, ok := token.Claims.(*kitekey.KiteClaims)
if !ok {
return errors.New("token does not have valid claims")
}
if claims.Audience == "" {
return errors.New("token has no audience")
}
if claims.Subject == "" {
return errors.New("token has no username")
}
// check if we have an audience and it matches our own signature
if err := k.verifyAudienceFunc(k.Kite(), claims.Audience); err != nil {
return err
}
// We don't check for exp and nbf claims here because jwt-go package
// already checks them.
// replace the requester username so we reflect the validated
r.Username = claims.Subject
return nil
}
// AuthenticateFromKiteKey authenticates user from kite key.
func (k *Kite) AuthenticateFromKiteKey(r *Request) error {
claims := &kitekey.KiteClaims{}
token, err := jwt.ParseWithClaims(r.Auth.Key, claims, k.verify)
if err != nil {
return err
}
if !token.Valid {
return errors.New("Invalid signature in kite key")
}
if claims.Subject == "" {
return errors.New("token has no username")
}
r.Username = claims.Subject
return nil
}
// AuthenticateSimpleKiteKey authenticates user from the given kite key and
// returns the authenticated username. It's the same as AuthenticateFromKiteKey
// but can be used without the need for a *kite.Request.
func (k *Kite) AuthenticateSimpleKiteKey(key string) (string, error) {
claims := &kitekey.KiteClaims{}
token, err := jwt.ParseWithClaims(key, claims, k.verify)
if err != nil {
return "", err
}
if !token.Valid {
return "", errors.New("Invalid signature in token")
}
if claims.Subject == "" {
return "", errors.New("token has no username")
}
return claims.Subject, nil
}
func (k *Kite) verifyInit() {
k.configMu.Lock()
defer k.configMu.Unlock()
k.verifyFunc = k.Config.VerifyFunc
if k.verifyFunc == nil {
k.verifyFunc = k.selfVerify
}
k.verifyAudienceFunc = k.Config.VerifyAudienceFunc
if k.verifyAudienceFunc == nil {
k.verifyAudienceFunc = k.verifyAudience
}
ttl := k.Config.VerifyTTL
if ttl == 0 {
ttl = 5 * time.Minute
}
if ttl > 0 {
k.mu.Lock()
k.verifyCache = cache.NewMemoryWithTTL(ttl)
k.mu.Unlock()
k.verifyCache.StartGC(ttl / 2)
}
key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(k.Config.KontrolKey))
if err != nil {
k.Log.Error("unable to init kontrol key: %s", err)
return
}
k.kontrolKey = key
}
func (k *Kite) selfVerify(pub string) error {
k.configMu.RLock()
ourKey := k.Config.KontrolKey
k.configMu.RUnlock()
if pub != ourKey {
return ErrKeyNotTrusted
}
return nil
}
func (k *Kite) verify(token *jwt.Token) (interface{}, error) {
k.verifyOnce.Do(k.verifyInit)
key := token.Claims.(*kitekey.KiteClaims).KontrolKey
if key == "" {
return nil, errors.New("no kontrol key found")
}
rsaKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(key))
if err != nil {
return nil, err
}
switch {
case k.verifyCache != nil:
v, err := k.verifyCache.Get(key)
if err != nil {
break
}
if !v.(bool) {
return nil, errors.New("invalid kontrol key found")
}
return rsaKey, nil
}
if err := k.verifyFunc(key); err != nil {
if err == ErrKeyNotTrusted {
k.verifyCache.Set(key, false)
}
// signal old token to somewhere else (GetKiteKey and alike)
return nil, err
}
k.verifyCache.Set(key, true)
return rsaKey, nil
}
func (k *Kite) verifyAudience(kite *protocol.Kite, audience string) error {
switch audience {
case "/":
// The root audience is like superuser - it has access to everything.
return nil
case "":
return errors.New("invalid empty audience")
}
aud, err := protocol.KiteFromString(audience)
if err != nil {
return fmt.Errorf("invalid audience: %s (%s)", err, audience)
}
// We verify the Username / Environment / Name matches the kite.
// Empty field (except username) is like wildcard - it matches all values.
if kite.Username != aud.Username {
return fmt.Errorf("audience: username %q not allowed (%s)", aud.Username, audience)
}
if kite.Environment != aud.Environment && aud.Environment != "" {
return fmt.Errorf("audience: environment %q not allowed (%s)", aud.Environment, audience)
}
if kite.Name != aud.Name && aud.Name != "" {
return fmt.Errorf("audience: kite %q not allowed (%s)", aud.Name, audience)
}
return nil
}