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
|
package credentials
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/openfga/go-sdk/oauth2/clientcredentials"
)
const ApiTokenHeaderKey = "Authorization"
const ApiTokenHeaderValuePrefix = "Bearer"
// Available credential methods
type CredentialsMethod string
const (
// No credentials (default)
CredentialsMethodNone CredentialsMethod = "none"
// API Token credentials (will be sent in "Authorization: Bearer $TOKEN" header)
CredentialsMethodApiToken CredentialsMethod = "api_token"
// Client Credentials flow will be performed, resulting token will be sent in "Authorization: Bearer $TOKEN" header
CredentialsMethodClientCredentials CredentialsMethod = "client_credentials"
)
type Config struct {
ApiToken string `json:"apiToken,omitempty"`
ClientCredentialsApiTokenIssuer string `json:"apiTokenIssuer,omitempty"`
ClientCredentialsApiAudience string `json:"apiAudience,omitempty"`
ClientCredentialsClientId string `json:"clientId,omitempty"`
ClientCredentialsClientSecret string `json:"clientSecret,omitempty"`
ClientCredentialsScopes string `json:"scopes,omitempty"`
}
type Credentials struct {
Method CredentialsMethod `json:"method,omitempty"`
Config *Config `json:"config,omitempty"`
Context context.Context
}
func NewCredentials(config Credentials) (*Credentials, error) {
creds := &Credentials{
Method: config.Method,
Config: config.Config,
}
if creds.Method == "" {
creds.Method = CredentialsMethodNone
}
err := creds.ValidateCredentialsConfig()
if err != nil {
return nil, err
}
return creds, nil
}
func (c *Credentials) ValidateCredentialsConfig() error {
conf := c.Config
if c.Method == CredentialsMethodApiToken && (conf == nil || conf.ApiToken == "") {
return fmt.Errorf("CredentialsConfig.ApiToken is required when CredentialsMethod is CredentialsMethodApiToken (%s)", c.Method)
} else if c.Method == CredentialsMethodClientCredentials {
if conf == nil ||
conf.ClientCredentialsClientId == "" ||
conf.ClientCredentialsClientSecret == "" ||
conf.ClientCredentialsApiTokenIssuer == "" {
return fmt.Errorf("all of CredentialsConfig.ClientId, CredentialsConfig.ClientSecret and CredentialsConfig.ApiTokenIssuer are required when CredentialsMethod is CredentialsMethodClientCredentials (%s)", c.Method)
}
tokenURL, err := buildApiTokenURL(conf.ClientCredentialsApiTokenIssuer)
if err != nil {
return err
}
conf.ClientCredentialsApiTokenIssuer = tokenURL
}
return nil
}
type HeaderParams struct {
Key string
Value string
}
func (c *Credentials) GetApiTokenHeader() *HeaderParams {
if c.Method != CredentialsMethodApiToken {
return nil
}
return &HeaderParams{
Key: ApiTokenHeaderKey,
Value: ApiTokenHeaderValuePrefix + " " + c.Config.ApiToken,
}
}
// GetHttpClientAndHeaderOverrides
// The main export the client uses to get a configuration with the necessary
// httpClient and header overrides based on the chosen credential method
func (c *Credentials) GetHttpClientAndHeaderOverrides() (*http.Client, []*HeaderParams) {
var headers []*HeaderParams
var client = http.DefaultClient
switch c.Method {
case CredentialsMethodClientCredentials:
ccConfig := clientcredentials.Config{
ClientID: c.Config.ClientCredentialsClientId,
ClientSecret: c.Config.ClientCredentialsClientSecret,
TokenURL: c.Config.ClientCredentialsApiTokenIssuer,
}
if c.Config.ClientCredentialsApiAudience != "" {
ccConfig.EndpointParams = map[string][]string{
"audience": {c.Config.ClientCredentialsApiAudience},
}
}
if c.Config.ClientCredentialsScopes != "" {
scopes := strings.Split(strings.TrimSpace(c.Config.ClientCredentialsScopes), " ")
ccConfig.Scopes = append(ccConfig.Scopes, scopes...)
}
if c.Context == nil {
c.Context = context.Background()
}
client = ccConfig.Client(c.Context)
case CredentialsMethodApiToken:
var header = c.GetApiTokenHeader()
if header != nil {
headers = append(headers, header)
}
case CredentialsMethodNone:
default:
}
return client, headers
}
var defaultTokenEndpointPath = "oauth/token"
func buildApiTokenURL(issuer string) (string, error) {
u, err := url.Parse(issuer)
if err != nil {
return "", err
}
if u.Scheme == "" {
u, _ = url.Parse(fmt.Sprintf("https://%s", issuer))
} else if u.Scheme != "http" && u.Scheme != "https" {
return "", fmt.Errorf("invalid issuer scheme '%s' (must be http or https)", u.Scheme)
}
if u.Path == "" || u.Path == "/" {
u.Path = defaultTokenEndpointPath
}
return u.String(), nil
}
|