git.iko.soy/typeable/oauth2-proxy
on master help sign in
Feature/add option to skip loading claims from profile url (#2329)

* add new flag skip-claims-from-profile-url * skip passing profile URL if SkipClaimsFromProfileURL * docs for --skip-claims-from-profile-url flag * update flag comment * update docs * update CHANGELOG.md * Update providers/provider_data.go Co-authored-by: Jan Larwig <jan@larwig.com> * Add tests for SkipClaimsFromProfileURL * simplify tests for SkipClaimsFromProfileURL * generate alpha_config.md --------- Co-authored-by: Jan Larwig <jan@larwig.com>

commit4c2bf5a2fe8cd751f7f93234a2c880c249a341fe authorNils Gustav Stråbø <65334626+nilsgstrabo@users.noreply.github.com> dateSat 20 Jan 2024, 20:51 (3 years ago) parent184c582
8 files changed+78−28
CHANGELOG.md+1 docs/docs/configuration/alpha_config.md+1 docs/docs/configuration/overview.md+1 pkg/apis/options/legacy_options.go+17−14 pkg/apis/options/providers.go+3 providers/provider_data.go+12−6 providers/provider_data_test.go+42−8 providers/providers.go+1
CHANGELOG.md+1
@@ -21,6 +21,7 @@
2121- [#1866](https://github.com/oauth2-proxy/oauth2-proxy/pull/1866) Add support for unix socker as upstream (@babs)
2222- [#1949](https://github.com/oauth2-proxy/oauth2-proxy/pull/1949) Allow cookie names with dots in redis sessions (@miguelborges99)
2323- [#2297](https://github.com/oauth2-proxy/oauth2-proxy/pull/2297) Add nightly build and push (@tuunit)
24+- [#2329](https://github.com/oauth2-proxy/oauth2-proxy/pull/2329) Add an option to skip request to profile URL for resolving missing claims in id_token (@nilsgstrabo)
2425- [#2299](https://github.com/oauth2-proxy/oauth2-proxy/pull/2299) bugfix: OIDCConfig based providers are not respecting flags and configs (@tuunit)
2526- [#2343](https://github.com/oauth2-proxy/oauth2-proxy/pull/2343) chore: Added checksums for .tar.gz (@kvanzuijlen)
2627- [#2248](https://github.com/oauth2-proxy/oauth2-proxy/pull/2248) Added support for semicolons in query strings. (@timwsuqld)
docs/docs/configuration/alpha_config.md+1
@@ -434,6 +434,7 @@ Provider holds all configuration for a single provider
434434| `loginURLParameters` | _[[]LoginURLParameter](#loginurlparameter)_ | LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL |
435435| `redeemURL` | _string_ | RedeemURL is the token redemption endpoint |
436436| `profileURL` | _string_ | ProfileURL is the profile access endpoint |
437+| `skipClaimsFromProfileURL` | _bool_ | SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token<br/>default set to 'false' |
437438| `resource` | _string_ | ProtectedResource is the resource that is protected (Azure AD and ADFS only) |
438439| `validateURL` | _string_ | ValidateURL is the access token validation endpoint |
439440| `scope` | _string_ | Scope is the OAuth scope specification |
docs/docs/configuration/overview.md+1
@@ -144,6 +144,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
144144| `--pass-host-header` | bool | pass the request Host Header to upstream | true |
145145| `--pass-user-headers` | bool | pass X-Forwarded-User, X-Forwarded-Groups, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true |
146146| `--profile-url` | string | Profile access endpoint | |
147+| `--skip-claims-from-profile-url` | bool | skip request to Profile URL for resolving claims not present in id_token | false |
147148| `--prompt` | string | [OIDC prompt](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest); if present, `approval-prompt` is ignored | `""` |
148149| `--provider` | string | OAuth provider | google |
149150| `--provider-ca-file` | string \| list | Paths to CA certificates that should be used when connecting to the provider. If not specified, the default Go trust sources are used instead. |
pkg/apis/options/legacy_options.go+17−14
@@ -523,6 +523,7 @@ type LegacyProvider struct {
523523 LoginURL string `flag:"login-url" cfg:"login_url"`
524524 RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
525525 ProfileURL string `flag:"profile-url" cfg:"profile_url"`
526+ SkipClaimsFromProfileURL bool `flag:"skip-claims-from-profile-url" cfg:"skip_claims_from_profile_url"`
526527 ProtectedResource string `flag:"resource" cfg:"resource"`
527528 ValidateURL string `flag:"validate-url" cfg:"validate_url"`
528529 Scope string `flag:"scope" cfg:"scope"`
@@ -578,6 +579,7 @@ func legacyProviderFlagSet() *pflag.FlagSet {
578579 flagSet.String("login-url", "", "Authentication endpoint")
579580 flagSet.String("redeem-url", "", "Token redemption endpoint")
580581 flagSet.String("profile-url", "", "Profile access endpoint")
582+ flagSet.Bool("skip-claims-from-profile-url", false, "Skip loading missing claims from profile URL")
581583 flagSet.String("resource", "", "The resource that is protected (Azure AD only)")
582584 flagSet.String("validate-url", "", "Access token validation endpoint")
583585 flagSet.String("scope", "", "OAuth scope specification")
@@ -658,20 +660,21 @@ func (l *LegacyProvider) convert() (Providers, error) {
658660 providers := Providers{}
659661
660662 provider := Provider{
661− ClientID: l.ClientID,
662− ClientSecret: l.ClientSecret,
663− ClientSecretFile: l.ClientSecretFile,
664− Type: ProviderType(l.ProviderType),
665− CAFiles: l.ProviderCAFiles,
666− UseSystemTrustStore: l.UseSystemTrustStore,
667− LoginURL: l.LoginURL,
668− RedeemURL: l.RedeemURL,
669− ProfileURL: l.ProfileURL,
670− ProtectedResource: l.ProtectedResource,
671− ValidateURL: l.ValidateURL,
672− Scope: l.Scope,
673− AllowedGroups: l.AllowedGroups,
674− CodeChallengeMethod: l.CodeChallengeMethod,
663+ ClientID: l.ClientID,
664+ ClientSecret: l.ClientSecret,
665+ ClientSecretFile: l.ClientSecretFile,
666+ Type: ProviderType(l.ProviderType),
667+ CAFiles: l.ProviderCAFiles,
668+ UseSystemTrustStore: l.UseSystemTrustStore,
669+ LoginURL: l.LoginURL,
670+ RedeemURL: l.RedeemURL,
671+ ProfileURL: l.ProfileURL,
672+ SkipClaimsFromProfileURL: l.SkipClaimsFromProfileURL,
673+ ProtectedResource: l.ProtectedResource,
674+ ValidateURL: l.ValidateURL,
675+ Scope: l.Scope,
676+ AllowedGroups: l.AllowedGroups,
677+ CodeChallengeMethod: l.CodeChallengeMethod,
675678 }
676679
677680 // This part is out of the switch section for all providers that support OIDC
pkg/apis/options/providers.go+3
@@ -70,6 +70,9 @@ type Provider struct {
7070 RedeemURL string `json:"redeemURL,omitempty"`
7171 // ProfileURL is the profile access endpoint
7272 ProfileURL string `json:"profileURL,omitempty"`
73+ // SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token
74+ // default set to 'false'
75+ SkipClaimsFromProfileURL bool `json:"skipClaimsFromProfileURL,omitempty"`
7376 // ProtectedResource is the resource that is protected (Azure AD and ADFS only)
7477 ProtectedResource string `json:"resource,omitempty"`
7578 // ValidateURL is the access token validation endpoint
providers/provider_data.go+12−6
@@ -43,11 +43,12 @@ type ProviderData struct {
4343 SupportedCodeChallengeMethods []string `json:"code_challenge_methods_supported,omitempty"`
4444
4545 // Common OIDC options for any OIDC-based providers to consume
46− AllowUnverifiedEmail bool
47− UserClaim string
48− EmailClaim string
49− GroupsClaim string
50− Verifier internaloidc.IDTokenVerifier
46+ AllowUnverifiedEmail bool
47+ UserClaim string
48+ EmailClaim string
49+ GroupsClaim string
50+ Verifier internaloidc.IDTokenVerifier
51+ SkipClaimsFromProfileURL bool
5152
5253 // Universal Group authorization data structure
5354 // any provider can set to consume
@@ -283,7 +284,12 @@ func (p *ProviderData) buildSessionFromClaims(rawIDToken, accessToken string) (*
283284}
284285
285286func (p *ProviderData) getClaimExtractor(rawIDToken, accessToken string) (util.ClaimExtractor, error) {
286− extractor, err := util.NewClaimExtractor(context.TODO(), rawIDToken, p.ProfileURL, p.getAuthorizationHeader(accessToken))
287+ profileURL := p.ProfileURL
288+ if p.SkipClaimsFromProfileURL {
289+ profileURL = &url.URL{}
290+ }
291+
292+ extractor, err := util.NewClaimExtractor(context.TODO(), rawIDToken, profileURL, p.getAuthorizationHeader(accessToken))
287293 if err != nil {
288294 return nil, fmt.Errorf("could not initialise claim extractor: %v", err)
289295 }
providers/provider_data_test.go+42−8
@@ -8,6 +8,8 @@ import (
88 "encoding/json"
99 "errors"
1010 "fmt"
11+ "net/http"
12+ "net/http/httptest"
1113 "net/url"
1214 "strings"
1315 "testing"
@@ -233,13 +235,16 @@ func TestProviderData_verifyIDToken(t *testing.T) {
233235
234236func TestProviderData_buildSessionFromClaims(t *testing.T) {
235237 testCases := map[string]struct {
236− IDToken idTokenClaims
237− AllowUnverified bool
238− UserClaim string
239− EmailClaim string
240− GroupsClaim string
241− ExpectedError error
242− ExpectedSession *sessions.SessionState
238+ IDToken idTokenClaims
239+ AllowUnverified bool
240+ UserClaim string
241+ EmailClaim string
242+ GroupsClaim string
243+ SkipClaimsFromProfileURL bool
244+ SetProfileURL bool
245+ ExpectedError error
246+ ExpectedSession *sessions.SessionState
247+ ExpectProfileURLCalled bool
243248 }{
244249 "Standard": {
245250 IDToken: defaultIDToken,
@@ -408,11 +413,36 @@ func TestProviderData_buildSessionFromClaims(t *testing.T) {
408413 PreferredUsername: "Jane Dobbs",
409414 },
410415 },
416+ "Request claims from ProfileURL": {
417+ IDToken: minimalIDToken,
418+ SetProfileURL: true,
419+ ExpectProfileURLCalled: true,
420+ ExpectedSession: &sessions.SessionState{},
421+ },
422+ "Skip claims request to ProfileURL": {
423+ IDToken: minimalIDToken,
424+ SetProfileURL: true,
425+ SkipClaimsFromProfileURL: true,
426+ ExpectedSession: &sessions.SessionState{},
427+ },
411428 }
412429 for testName, tc := range testCases {
413430 t.Run(testName, func(t *testing.T) {
414431 g := NewWithT(t)
415432
433+ var (
434+ profileURL *url.URL
435+ profileURLCalled bool
436+ )
437+ if tc.SetProfileURL {
438+ profileURLSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
439+ profileURLCalled = true
440+ w.Write([]byte("{}"))
441+ }))
442+ defer profileURLSrv.Close()
443+ profileURL, _ = url.Parse(profileURLSrv.URL)
444+ }
445+
416446 verificationOptions := internaloidc.IDTokenVerificationOptions{
417447 AudienceClaims: []string{"aud"},
418448 ClientID: oidcClientID,
@@ -423,22 +453,26 @@ func TestProviderData_buildSessionFromClaims(t *testing.T) {
423453 mockJWKS{},
424454 &oidc.Config{ClientID: oidcClientID},
425455 ), verificationOptions),
456+ ProfileURL: profileURL,
457+ getAuthorizationHeaderFunc: func(s string) http.Header { return http.Header{} },
426458 }
427459 provider.AllowUnverifiedEmail = tc.AllowUnverified
428460 provider.UserClaim = tc.UserClaim
429461 provider.EmailClaim = tc.EmailClaim
430462 provider.GroupsClaim = tc.GroupsClaim
463+ provider.SkipClaimsFromProfileURL = tc.SkipClaimsFromProfileURL
431464
432465 rawIDToken, err := newSignedTestIDToken(tc.IDToken)
433466 g.Expect(err).ToNot(HaveOccurred())
434467
435− ss, err := provider.buildSessionFromClaims(rawIDToken, "")
468+ ss, err := provider.buildSessionFromClaims(rawIDToken, "testtoken")
436469 if err != nil {
437470 g.Expect(err).To(Equal(tc.ExpectedError))
438471 }
439472 if ss != nil {
440473 g.Expect(ss).To(Equal(tc.ExpectedSession))
441474 }
475+ g.Expect(profileURLCalled).To(Equal(tc.ExpectProfileURLCalled))
442476 })
443477 }
444478}
providers/providers.go+1
@@ -138,6 +138,7 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
138138 p.AllowUnverifiedEmail = providerConfig.OIDCConfig.InsecureAllowUnverifiedEmail
139139 p.EmailClaim = providerConfig.OIDCConfig.EmailClaim
140140 p.GroupsClaim = providerConfig.OIDCConfig.GroupsClaim
141+ p.SkipClaimsFromProfileURL = providerConfig.SkipClaimsFromProfileURL
141142
142143 // Set PKCE enabled or disabled based on discovery and force options
143144 p.CodeChallengeMethod = parseCodeChallengeMethod(providerConfig)