mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
* chore: upgrade otel dependencies to version v0.129.0 * remove unused mocks * fix linting * add source headers * remove formatting
101 lines
2.6 KiB
Go
101 lines
2.6 KiB
Go
// Package oauth provides OAUTHBEARER sasl authentication as specified in
|
|
// RFC7628.
|
|
package oauth
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
|
|
"github.com/twmb/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
|
|
}
|
|
if auth.Token == "" {
|
|
return nil, nil, errors.New("OAUTHBEARER token must be non-empty")
|
|
}
|
|
|
|
// 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
|
|
}
|