forked from koding/kite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
request.go
308 lines (257 loc) · 8.41 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
package kite
import (
"errors"
"fmt"
"runtime/debug"
"strings"
"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"
)
// Request contains information about the incoming request.
type Request struct {
// Method defines the method name which is invoked by the incoming request
Method 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
// Username defines the username which the incoming request is bound to.
// This is authenticated and validated if authentication is enabled.
Username string
// 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.
Context cache.Cache
}
// 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(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, 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.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.",
})
return
}
// Call the handler functions.
result, err := method.ServeKite(request)
callFunc(result, createError(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{
Method: method,
Args: options.WithArgs,
LocalKite: c.LocalKite,
Client: c,
Auth: options.Auth,
Context: cache.NewMemory(),
}
// 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: err.Error(),
}
}
// 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 {
token, err := jwt.Parse(r.Auth.Key, r.LocalKite.RSAKey)
if err != nil {
return err
}
if !token.Valid {
return errors.New("Invalid signature in token")
}
// check if we have an audience and it matches our own signature
audience, ok := token.Claims["aud"].(string)
if ok && audience != "/" {
if checkAudience(k.Kite().String(), audience); err != nil {
return err
}
}
// We don't check for exp and nbf claims here because jwt-go package
// already checks them.
username, ok := token.Claims["sub"].(string)
if !ok {
return errors.New("Username is not present in token")
}
// replace the requester username so we reflect the validated
r.Username = username
return nil
}
func checkAudience(kiteRepr, audience string) error {
a, err := protocol.KiteFromString(audience)
if err != nil {
return err
}
// it doesn't make sense to return an error if the audience is fully empty
if a.Username == "" {
return nil
}
// this is good so our kites can also work behind load balancers
threePart := fmt.Sprintf("/%s/%s/%s", a.Username, a.Environment, a.Name)
// now check if the first three fields are matching our own fields
if !strings.HasPrefix(kiteRepr, threePart) {
return fmt.Errorf("Invalid audience in token. Have: '%s' Must be a part of: '%s'",
audience, kiteRepr)
}
return nil
}
// AuthenticateFromKiteKey authenticates user from kite key.
func (k *Kite) AuthenticateFromKiteKey(r *Request) error {
token, err := jwt.Parse(r.Auth.Key, kitekey.GetKontrolKey)
if err != nil {
return err
}
if !token.Valid {
return errors.New("Invalid signature in token")
}
if username, ok := token.Claims["sub"].(string); !ok {
return errors.New("Username is not present in token")
} else {
r.Username = username
}
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) {
token, err := jwt.Parse(key, kitekey.GetKontrolKey)
if err != nil {
return "", err
}
if !token.Valid {
return "", errors.New("Invalid signature in token")
}
username, ok := token.Claims["sub"].(string)
if !ok {
return "", errors.New("Username is not present in token")
}
// return authenticated username
return username, nil
}