forked from twmb/franz-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
oauth.go
97 lines (83 loc) · 2.5 KB
/
oauth.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
// Package oauth provides OAUTHBEARER sasl authentication as specified in
// RFC7628.
package oauth
import (
"context"
"errors"
"sort"
"github.com/LSparkzwz/franz-go/pkg/sasl"
)
// Auth contains information for authentication.
//
// This client may add fields to this struct in the future if Kafka adds more
// capabilities to Oauth.
type Auth struct {
// Zid is an optional authorization ID to use in authenticating.
Zid string
// Token is the oauthbearer token to use for a single session's
// authentication.
Token string
// Extensions are key value pairs to add to the authentication request.
Extensions map[string]string
_ struct{} // require explicit field initialization
}
// AsMechanism returns a sasl mechanism that will use 'a' as credentials for
// all sasl sessions.
//
// This is a shortcut for using the Oauth function and is useful when you do
// not need to live-rotate credentials.
func (a Auth) AsMechanism() sasl.Mechanism {
return Oauth(func(context.Context) (Auth, error) {
return a, nil
})
}
// Oauth returns an OAUTHBEARER sasl mechanism that will call authFn whenever
// authentication is needed. The returned Auth is used for a single session.
func Oauth(authFn func(context.Context) (Auth, error)) sasl.Mechanism {
return oauth(authFn)
}
type oauth func(context.Context) (Auth, error)
func (oauth) Name() string { return "OAUTHBEARER" }
func (fn oauth) Authenticate(ctx context.Context, _ string) (sasl.Session, []byte, error) {
auth, err := fn(ctx)
if err != nil {
return nil, nil, err
}
// We sort extensions for consistency, but it is not required.
type kv struct {
k string
v string
}
kvs := make([]kv, 0, len(auth.Extensions))
for k, v := range auth.Extensions {
if len(k) == 0 {
continue
}
kvs = append(kvs, kv{k, v})
}
sort.Slice(kvs, func(i, j int) bool { return kvs[i].k < kvs[j].k })
// https://tools.ietf.org/html/rfc7628#section-3.1
gs2Header := "n," // no channel binding
if auth.Zid != "" {
gs2Header += "a=" + auth.Zid
}
gs2Header += ","
init := []byte(gs2Header + "\x01auth=Bearer ")
init = append(init, auth.Token...)
init = append(init, '\x01')
for _, kv := range kvs {
init = append(init, kv.k...)
init = append(init, '=')
init = append(init, kv.v...)
init = append(init, '\x01')
}
init = append(init, '\x01')
return session{}, init, nil
}
type session struct{}
func (session) Challenge(resp []byte) (bool, []byte, error) {
if len(resp) != 0 {
return false, nil, errors.New("unexpected data in oauth response")
}
return true, nil, nil
}