Repository navigation
Expand file tree
/
Copy pathprovider.go
More file actions
181 lines (146 loc) · 5.2 KB
/
Copy pathprovider.go
File metadata and controls
181 lines (146 loc) · 5.2 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
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package caddy_oidc
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/relvacode/caddy-oidc/authenticator"
"github.com/relvacode/caddy-oidc/internal/deferred"
"github.com/relvacode/caddy-oidc/request"
"github.com/relvacode/caddy-oidc/template"
"go.uber.org/zap"
"golang.org/x/oauth2"
)
type discoveryConfiguration struct {
HTTPClient *http.Client
Provider *oidc.Provider
Verifier template.TokenVerifier
OAuth2 *template.OAuth2ConfigTemplate
TokenParams map[string]string
}
func (cfg *discoveryConfiguration) AuthCodeURL(ctx context.Context, state string, opts ...oauth2.AuthCodeOption) (string, error) {
repl := template.MustReplacer(ctx)
oauthCfg, err := cfg.OAuth2.Replace(repl)
if err != nil {
return "", err
}
return oauthCfg.AuthCodeURL(state, opts...), nil
}
func (cfg *discoveryConfiguration) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
repl := template.MustReplacer(ctx)
ctx = context.WithValue(ctx, oauth2.HTTPClient, cfg.HTTPClient)
oauthCfg, err := cfg.OAuth2.Replace(repl)
if err != nil {
return nil, err
}
if len(cfg.TokenParams) > 0 {
for urlParam, v := range cfg.TokenParams {
pv, err := repl.ReplaceOrErr(v, false, true)
if err != nil {
return nil, fmt.Errorf("failed to replace token param %s: %w", urlParam, err)
}
opts = append(opts, oauth2.SetAuthURLParam(urlParam, pv))
}
}
return oauthCfg.Exchange(ctx, code, opts...)
}
func (cfg *discoveryConfiguration) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*oidc.UserInfo, error) {
ctx = context.WithValue(ctx, oauth2.HTTPClient, cfg.HTTPClient)
return cfg.Provider.UserInfo(ctx, tokenSource)
}
var (
_ authenticator.OIDCConfiguration = (*Provider)(nil)
_ authenticator.OAuthAuthorizationFlowConfiguration = (*Provider)(nil)
)
// Provider holds the built configuration for an OIDC provider and authentication logic.
type Provider struct {
Log *zap.Logger
Clock func() time.Time
Issuer string
UsernameClaim string
ProtectedResource *ProtectedResourceMetadataConfiguration
Authenticators authenticator.Set
Discovery *deferred.Result[*discoveryConfiguration]
}
func (pr *Provider) Now() time.Time { return pr.Clock() }
func (pr *Provider) GetUsernameClaim() string { return pr.UsernameClaim }
func (pr *Provider) GetVerifier(ctx context.Context) (template.TokenVerifier, error) {
discovery, err := pr.Discovery.Get(ctx)
if err != nil {
return nil, err
}
return discovery.Verifier, nil
}
func (pr *Provider) AuthCodeURL(ctx context.Context, state string, opts ...oauth2.AuthCodeOption) (string, error) {
discovery, err := pr.Discovery.Get(ctx)
if err != nil {
return "", err
}
return discovery.AuthCodeURL(ctx, state, opts...)
}
func (pr *Provider) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
discovery, err := pr.Discovery.Get(ctx)
if err != nil {
return nil, err
}
return discovery.Exchange(ctx, code, opts...)
}
func (pr *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*oidc.UserInfo, error) {
discovery, err := pr.Discovery.Get(ctx)
if err != nil {
return nil, err
}
return discovery.UserInfo(ctx, tokenSource)
}
// ProtectedResourceMetadata returns the OAuth protected resource metadata for this authenticator.
// If protected resource metadata is not enabled, then false is returned.
func (pr *Provider) ProtectedResourceMetadata(r *http.Request) (*OAuthProtectedResource, bool) {
if pr.ProtectedResource.Disable {
return nil, false
}
discovery, err := pr.Discovery.Get(r.Context())
if err != nil {
return nil, false
}
var (
requestURL = request.URL(r)
metadata = &OAuthProtectedResource{
Resource: fmt.Sprintf("%s://%s", requestURL.Scheme, requestURL.Host),
ScopesSupported: discovery.OAuth2.Scopes,
AuthorizationServers: []string{
pr.Issuer,
},
// OIDC middleware only supports bearer authentication via the Authorization header
BearerMethodsSupported: []string{
"header",
},
}
)
if pr.ProtectedResource.Audience {
metadata.Audience, err = discovery.OAuth2.ClientID(template.MustReplacer(r.Context()))
if err != nil {
return nil, false
}
}
return metadata, true
}
// WellKnownOAuthProtectedResourcePath is the path for the OAuth protected resource metadata endpoint.
const WellKnownOAuthProtectedResourcePath = "/.well-known/oauth-protected-resource"
// ServeHTTPOAuthProtectedResource returns the OAuth protected resource metadata for the endpoint
// .well-known/oauth-protected-resource.
// If the endpoint is disabled, then a 404 not found response is returned.
func (pr *Provider) ServeHTTPOAuthProtectedResource(rw http.ResponseWriter, r *http.Request) error {
metadata, ok := pr.ProtectedResourceMetadata(r)
if !ok {
return caddyhttp.Error(http.StatusNotFound, errors.New("protected resource metadata is disabled"))
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
enc := json.NewEncoder(rw)
enc.SetIndent("", " ")
return enc.Encode(metadata)
}