-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathexchange.go
More file actions
128 lines (104 loc) · 2.89 KB
/
exchange.go
File metadata and controls
128 lines (104 loc) · 2.89 KB
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
package sdk
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"github.com/openfaas/go-sdk/internal/httpclient"
)
// Exchange an OIDC ID Token from an IdP for OpenFaaS token
// using the token exchange grant type.
// tokenURL should be the OpenFaaS token endpoint within the internal OIDC service
func ExchangeIDToken(tokenURL, rawIDToken string, options ...ExchangeOption) (*Token, error) {
c := &ExchangeConfig{
Client: http.DefaultClient,
}
for _, option := range options {
option(c)
}
v := url.Values{}
v.Set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
v.Set("subject_token_type", "urn:ietf:params:oauth:token-type:id_token")
v.Set("subject_token", rawIDToken)
for _, aud := range c.Audience {
v.Add("audience", aud)
}
if len(c.Scope) > 0 {
v.Set("scope", strings.Join(c.Scope, " "))
}
u, _ := url.Parse(tokenURL)
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(v.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "openfaas-go-sdk")
if os.Getenv("FAAS_DEBUG") == "1" {
dump, err := httpclient.DumpRequest(req)
if err != nil {
return nil, err
}
fmt.Println(dump)
}
res, err := c.Client.Do(req)
if err != nil {
return nil, err
}
if res.Body != nil {
defer res.Body.Close()
}
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("cannot fetch token: %v", err)
}
if res.StatusCode == http.StatusBadRequest {
authErr := &OAuthError{}
if err := json.Unmarshal(body, authErr); err == nil {
return nil, authErr
}
}
if code := res.StatusCode; code < 200 || code > 299 {
return nil, fmt.Errorf("unexpected status code: %v\nResponse: %s", res.Status, body)
}
tj := &tokenJSON{}
if err := json.Unmarshal(body, tj); err != nil {
return nil, fmt.Errorf("unable to unmarshal token: %s", err)
}
return &Token{
IDToken: tj.AccessToken,
Expiry: tj.expiry(),
Scope: tj.scope(),
}, nil
}
type ExchangeConfig struct {
Audience []string
Scope []string
Client *http.Client
}
// ExchangeOption is used to implement functional-style options that modify the
// config setting for the OpenFaaS token exchange.
type ExchangeOption func(*ExchangeConfig)
// WithAudience is an option to configure the audience requested
// in the token exchange.
func WithAudience(audience []string) ExchangeOption {
return func(c *ExchangeConfig) {
c.Audience = audience
}
}
// WithScope is an option to configure the scope requested
// in the token exchange.
func WithScope(scope []string) ExchangeOption {
return func(c *ExchangeConfig) {
c.Scope = scope
}
}
// WithHttpClient is an option to configure the http client
// used to make the token exchange request.
func WithHttpClient(client *http.Client) ExchangeOption {
return func(c *ExchangeConfig) {
c.Client = client
}
}