...

Source file src/github.com/twmb/franz-go/pkg/sasl/oauth/oauth.go

Documentation: github.com/twmb/franz-go/pkg/sasl/oauth

     1  // Package oauth provides OAUTHBEARER sasl authentication as specified in
     2  // RFC7628.
     3  package oauth
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"sort"
     9  
    10  	"github.com/twmb/franz-go/pkg/sasl"
    11  )
    12  
    13  // Auth contains information for authentication.
    14  //
    15  // This client may add fields to this struct in the future if Kafka adds more
    16  // capabilities to Oauth.
    17  type Auth struct {
    18  	// Zid is an optional authorization ID to use in authenticating.
    19  	Zid string
    20  
    21  	// Token is the oauthbearer token to use for a single session's
    22  	// authentication.
    23  	Token string
    24  	// Extensions are key value pairs to add to the authentication request.
    25  	Extensions map[string]string
    26  
    27  	_ struct{} // require explicit field initialization
    28  }
    29  
    30  // AsMechanism returns a sasl mechanism that will use 'a' as credentials for
    31  // all sasl sessions.
    32  //
    33  // This is a shortcut for using the Oauth function and is useful when you do
    34  // not need to live-rotate credentials.
    35  func (a Auth) AsMechanism() sasl.Mechanism {
    36  	return Oauth(func(context.Context) (Auth, error) {
    37  		return a, nil
    38  	})
    39  }
    40  
    41  // Oauth returns an OAUTHBEARER sasl mechanism that will call authFn whenever
    42  // authentication is needed. The returned Auth is used for a single session.
    43  func Oauth(authFn func(context.Context) (Auth, error)) sasl.Mechanism {
    44  	return oauth(authFn)
    45  }
    46  
    47  type oauth func(context.Context) (Auth, error)
    48  
    49  func (oauth) Name() string { return "OAUTHBEARER" }
    50  func (fn oauth) Authenticate(ctx context.Context, _ string) (sasl.Session, []byte, error) {
    51  	auth, err := fn(ctx)
    52  	if err != nil {
    53  		return nil, nil, err
    54  	}
    55  	if auth.Token == "" {
    56  		return nil, nil, errors.New("OAUTHBEARER token must be non-empty")
    57  	}
    58  
    59  	// We sort extensions for consistency, but it is not required.
    60  	type kv struct {
    61  		k string
    62  		v string
    63  	}
    64  	kvs := make([]kv, 0, len(auth.Extensions))
    65  	for k, v := range auth.Extensions {
    66  		if len(k) == 0 {
    67  			continue
    68  		}
    69  		kvs = append(kvs, kv{k, v})
    70  	}
    71  	sort.Slice(kvs, func(i, j int) bool { return kvs[i].k < kvs[j].k })
    72  
    73  	// https://tools.ietf.org/html/rfc7628#section-3.1
    74  	gs2Header := "n," // no channel binding
    75  	if auth.Zid != "" {
    76  		gs2Header += "a=" + auth.Zid
    77  	}
    78  	gs2Header += ","
    79  	init := []byte(gs2Header + "\x01auth=Bearer ")
    80  	init = append(init, auth.Token...)
    81  	init = append(init, '\x01')
    82  	for _, kv := range kvs {
    83  		init = append(init, kv.k...)
    84  		init = append(init, '=')
    85  		init = append(init, kv.v...)
    86  		init = append(init, '\x01')
    87  	}
    88  	init = append(init, '\x01')
    89  
    90  	return session{}, init, nil
    91  }
    92  
    93  type session struct{}
    94  
    95  func (session) Challenge(resp []byte) (bool, []byte, error) {
    96  	if len(resp) != 0 {
    97  		return false, nil, errors.New("unexpected data in oauth response")
    98  	}
    99  	return true, nil, nil
   100  }
   101  

View as plain text