Files
JaviandGitHub 5025fe7279 chore: upgrade otel dependencies to version v0.129.0 (#5365)
* chore: upgrade otel dependencies to version v0.129.0

* remove unused mocks

* fix linting

* add source headers

* remove formatting
2025-07-08 10:02:50 +00:00

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
}