File: credentials.go

package info (click to toggle)
golang-github-openfga-go-sdk 0.6.5-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid, trixie
  • size: 1,960 kB
  • sloc: makefile: 13
file content (153 lines) | stat: -rw-r--r-- 4,595 bytes parent folder | download
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
}