Move provider initialisation into providers package
This commit is contained in:
		
							parent
							
								
									95dd2745c7
								
							
						
					
					
						commit
						d162b018a8
					
				|  | @ -305,7 +305,7 @@ Provider holds all configuration for a single provider | ||||||
| | `oidcConfig` | _[OIDCOptions](#oidcoptions)_ | OIDCConfig holds all configurations for OIDC provider<br/>or providers utilize OIDC configurations. | | | `oidcConfig` | _[OIDCOptions](#oidcoptions)_ | OIDCConfig holds all configurations for OIDC provider<br/>or providers utilize OIDC configurations. | | ||||||
| | `loginGovConfig` | _[LoginGovOptions](#logingovoptions)_ | LoginGovConfig holds all configurations for LoginGov provider. | | | `loginGovConfig` | _[LoginGovOptions](#logingovoptions)_ | LoginGovConfig holds all configurations for LoginGov provider. | | ||||||
| | `id` | _string_ | ID should be a unique identifier for the provider.<br/>This value is required for all providers. | | | `id` | _string_ | ID should be a unique identifier for the provider.<br/>This value is required for all providers. | | ||||||
| | `provider` | _string_ | Type is the OAuth provider<br/>must be set from the supported providers group,<br/>otherwise 'Google' is set as default | | | `provider` | _[ProviderType](#providertype)_ | Type is the OAuth provider<br/>must be set from the supported providers group,<br/>otherwise 'Google' is set as default | | ||||||
| | `name` | _string_ | Name is the providers display name<br/>if set, it will be shown to the users in the login page. | | | `name` | _string_ | Name is the providers display name<br/>if set, it will be shown to the users in the login page. | | ||||||
| | `caFiles` | _[]string_ | CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.<br/>If not specified, the default Go trust sources are used instead | | | `caFiles` | _[]string_ | CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.<br/>If not specified, the default Go trust sources are used instead | | ||||||
| | `loginURL` | _string_ | LoginURL is the authentication endpoint | | | `loginURL` | _string_ | LoginURL is the authentication endpoint | | ||||||
|  | @ -319,6 +319,17 @@ Provider holds all configuration for a single provider | ||||||
| | `allowedGroups` | _[]string_ | AllowedGroups is a list of restrict logins to members of this group | | | `allowedGroups` | _[]string_ | AllowedGroups is a list of restrict logins to members of this group | | ||||||
| | `acrValues` | _string_ | AcrValues is a string of acr values | | | `acrValues` | _string_ | AcrValues is a string of acr values | | ||||||
| 
 | 
 | ||||||
|  | ### ProviderType | ||||||
|  | #### (`string` alias) | ||||||
|  | 
 | ||||||
|  | (**Appears on:** [Provider](#provider)) | ||||||
|  | 
 | ||||||
|  | ProviderType is used to enumerate the different provider type options | ||||||
|  | Valid options are: adfs, azure, bitbucket, digitalocean facebook, github, | ||||||
|  | gitlab, google, keycloak, keycloak-oidc, linkedin, login.gov, nextcloud | ||||||
|  | and oidc. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| ### Providers | ### Providers | ||||||
| 
 | 
 | ||||||
| #### ([[]Provider](#provider) alias) | #### ([[]Provider](#provider) alias) | ||||||
|  |  | ||||||
|  | @ -624,7 +624,7 @@ func (l *LegacyProvider) convert() (Providers, error) { | ||||||
| 		ClientID:          l.ClientID, | 		ClientID:          l.ClientID, | ||||||
| 		ClientSecret:      l.ClientSecret, | 		ClientSecret:      l.ClientSecret, | ||||||
| 		ClientSecretFile:  l.ClientSecretFile, | 		ClientSecretFile:  l.ClientSecretFile, | ||||||
| 		Type:              l.ProviderType, | 		Type:              ProviderType(l.ProviderType), | ||||||
| 		CAFiles:           l.ProviderCAFiles, | 		CAFiles:           l.ProviderCAFiles, | ||||||
| 		LoginURL:          l.LoginURL, | 		LoginURL:          l.LoginURL, | ||||||
| 		RedeemURL:         l.RedeemURL, | 		RedeemURL:         l.RedeemURL, | ||||||
|  |  | ||||||
|  | @ -52,7 +52,7 @@ type Provider struct { | ||||||
| 	// Type is the OAuth provider
 | 	// Type is the OAuth provider
 | ||||||
| 	// must be set from the supported providers group,
 | 	// must be set from the supported providers group,
 | ||||||
| 	// otherwise 'Google' is set as default
 | 	// otherwise 'Google' is set as default
 | ||||||
| 	Type string `json:"provider,omitempty"` | 	Type ProviderType `json:"provider,omitempty"` | ||||||
| 	// Name is the providers display name
 | 	// Name is the providers display name
 | ||||||
| 	// if set, it will be shown to the users in the login page.
 | 	// if set, it will be shown to the users in the login page.
 | ||||||
| 	Name string `json:"name,omitempty"` | 	Name string `json:"name,omitempty"` | ||||||
|  | @ -84,6 +84,56 @@ type Provider struct { | ||||||
| 	AcrValues string `json:"acrValues,omitempty"` | 	AcrValues string `json:"acrValues,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ProviderType is used to enumerate the different provider type options
 | ||||||
|  | // Valid options are: adfs, azure, bitbucket, digitalocean facebook, github,
 | ||||||
|  | // gitlab, google, keycloak, keycloak-oidc, linkedin, login.gov, nextcloud
 | ||||||
|  | // and oidc.
 | ||||||
|  | type ProviderType string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// ADFSProvider is the provider type for ADFS
 | ||||||
|  | 	ADFSProvider ProviderType = "adfs" | ||||||
|  | 
 | ||||||
|  | 	// AzureProvider is the provider type for Azure
 | ||||||
|  | 	AzureProvider ProviderType = "azure" | ||||||
|  | 
 | ||||||
|  | 	// BitbucketProvider is the provider type for Bitbucket
 | ||||||
|  | 	BitbucketProvider ProviderType = "bitbucket" | ||||||
|  | 
 | ||||||
|  | 	// DigitalOceanProvider is the provider type for DigitalOcean
 | ||||||
|  | 	DigitalOceanProvider ProviderType = "digitalocean" | ||||||
|  | 
 | ||||||
|  | 	// FacebookProvider is the provider type for Facebook
 | ||||||
|  | 	FacebookProvider ProviderType = "facebook" | ||||||
|  | 
 | ||||||
|  | 	// GitHubProvider is the provider type for GitHub
 | ||||||
|  | 	GitHubProvider ProviderType = "github" | ||||||
|  | 
 | ||||||
|  | 	// GitLabProvider is the provider type for GitLab
 | ||||||
|  | 	GitLabProvider ProviderType = "gitlab" | ||||||
|  | 
 | ||||||
|  | 	// GoogleProvider is the provider type for GoogleProvider
 | ||||||
|  | 	GoogleProvider ProviderType = "google" | ||||||
|  | 
 | ||||||
|  | 	// KeycloakProvider is the provider type for Keycloak
 | ||||||
|  | 	KeycloakProvider ProviderType = "keycloak" | ||||||
|  | 
 | ||||||
|  | 	// KeycloakOIDCProvider is the provider type for Keycloak OIDC
 | ||||||
|  | 	KeycloakOIDCProvider ProviderType = "keycloak-oidc" | ||||||
|  | 
 | ||||||
|  | 	// LinkedInProvider is the provider type for LinkedIn
 | ||||||
|  | 	LinkedInProvider ProviderType = "linkedin" | ||||||
|  | 
 | ||||||
|  | 	// LoginGovProvider is the provider type for LoginGov
 | ||||||
|  | 	LoginGovProvider ProviderType = "login.gov" | ||||||
|  | 
 | ||||||
|  | 	// NextCloudProvider is the provider type for NextCloud
 | ||||||
|  | 	NextCloudProvider ProviderType = "nextcloud" | ||||||
|  | 
 | ||||||
|  | 	// OIDCProvider is the provider type for OIDC
 | ||||||
|  | 	OIDCProvider ProviderType = "oidc" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| type KeycloakOptions struct { | type KeycloakOptions struct { | ||||||
| 	// Group enables to restrict login to members of indicated group
 | 	// Group enables to restrict login to members of indicated group
 | ||||||
| 	Groups []string `json:"groups,omitempty"` | 	Groups []string `json:"groups,omitempty"` | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -24,12 +25,11 @@ var _ Provider = (*ADFSProvider)(nil) | ||||||
| const ( | const ( | ||||||
| 	adfsProviderName = "ADFS" | 	adfsProviderName = "ADFS" | ||||||
| 	adfsDefaultScope = "openid email profile" | 	adfsDefaultScope = "openid email profile" | ||||||
| 	adfsSkipScope    = false |  | ||||||
| 	adfsUPNClaim     = "upn" | 	adfsUPNClaim     = "upn" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewADFSProvider initiates a new ADFSProvider
 | // NewADFSProvider initiates a new ADFSProvider
 | ||||||
| func NewADFSProvider(p *ProviderData) *ADFSProvider { | func NewADFSProvider(p *ProviderData, opts options.ADFSOptions) *ADFSProvider { | ||||||
| 	p.setProviderDefaults(providerDefaults{ | 	p.setProviderDefaults(providerDefaults{ | ||||||
| 		name:  adfsProviderName, | 		name:  adfsProviderName, | ||||||
| 		scope: adfsDefaultScope, | 		scope: adfsDefaultScope, | ||||||
|  | @ -53,17 +53,12 @@ func NewADFSProvider(p *ProviderData) *ADFSProvider { | ||||||
| 
 | 
 | ||||||
| 	return &ADFSProvider{ | 	return &ADFSProvider{ | ||||||
| 		OIDCProvider:    oidcProvider, | 		OIDCProvider:    oidcProvider, | ||||||
| 		skipScope:       adfsSkipScope, | 		skipScope:       opts.SkipScope, | ||||||
| 		oidcEnrichFunc:  oidcProvider.EnrichSession, | 		oidcEnrichFunc:  oidcProvider.EnrichSession, | ||||||
| 		oidcRefreshFunc: oidcProvider.RefreshSession, | 		oidcRefreshFunc: oidcProvider.RefreshSession, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Configure defaults the ADFSProvider configuration options
 |  | ||||||
| func (p *ADFSProvider) Configure(skipScope bool) { |  | ||||||
| 	p.skipScope = skipScope |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GetLoginURL Override to double encode the state parameter. If not query params are lost
 | // GetLoginURL Override to double encode the state parameter. If not query params are lost
 | ||||||
| // More info here: https://docs.microsoft.com/en-us/powerapps/maker/portals/configure/configure-saml2-settings
 | // More info here: https://docs.microsoft.com/en-us/powerapps/maker/portals/configure/configure-saml2-settings
 | ||||||
| func (p *ADFSProvider) GetLoginURL(redirectURI, state, nonce string) string { | func (p *ADFSProvider) GetLoginURL(redirectURI, state, nonce string) string { | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/coreos/go-oidc/v3/oidc" | 	"github.com/coreos/go-oidc/v3/oidc" | ||||||
| 	"github.com/golang-jwt/jwt" | 	"github.com/golang-jwt/jwt" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc" | 	internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc" | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
|  | @ -61,8 +62,8 @@ func testADFSProvider(hostname string) *ADFSProvider { | ||||||
| 		ValidateURL:  &url.URL{}, | 		ValidateURL:  &url.URL{}, | ||||||
| 		Scope:        "", | 		Scope:        "", | ||||||
| 		Verifier:     o, | 		Verifier:     o, | ||||||
| 		EmailClaim:   OIDCEmailClaim, | 		EmailClaim:   options.OIDCEmailClaim, | ||||||
| 	}) | 	}, options.ADFSOptions{}) | ||||||
| 
 | 
 | ||||||
| 	if hostname != "" { | 	if hostname != "" { | ||||||
| 		updateURL(p.Data().LoginURL, hostname) | 		updateURL(p.Data().LoginURL, hostname) | ||||||
|  | @ -133,7 +134,7 @@ var _ = Describe("ADFS Provider Tests", func() { | ||||||
| 
 | 
 | ||||||
| 	Context("New Provider Init", func() { | 	Context("New Provider Init", func() { | ||||||
| 		It("uses defaults", func() { | 		It("uses defaults", func() { | ||||||
| 			providerData := NewADFSProvider(&ProviderData{}).Data() | 			providerData := NewADFSProvider(&ProviderData{}, options.ADFSOptions{}).Data() | ||||||
| 			Expect(providerData.ProviderName).To(Equal("ADFS")) | 			Expect(providerData.ProviderName).To(Equal("ADFS")) | ||||||
| 			Expect(providerData.Scope).To(Equal("openid email profile")) | 			Expect(providerData.Scope).To(Equal("openid email profile")) | ||||||
| 		}) | 		}) | ||||||
|  | @ -165,8 +166,7 @@ var _ = Describe("ADFS Provider Tests", func() { | ||||||
| 			p := NewADFSProvider(&ProviderData{ | 			p := NewADFSProvider(&ProviderData{ | ||||||
| 				ProtectedResource: resource, | 				ProtectedResource: resource, | ||||||
| 				Scope:             "", | 				Scope:             "", | ||||||
| 			}) | 			}, options.ADFSOptions{SkipScope: true}) | ||||||
| 			p.skipScope = true |  | ||||||
| 
 | 
 | ||||||
| 			result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "") | 			result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "") | ||||||
| 			Expect(result).NotTo(ContainSubstring("scope=")) | 			Expect(result).NotTo(ContainSubstring("scope=")) | ||||||
|  | @ -186,7 +186,7 @@ var _ = Describe("ADFS Provider Tests", func() { | ||||||
| 				p := NewADFSProvider(&ProviderData{ | 				p := NewADFSProvider(&ProviderData{ | ||||||
| 					ProtectedResource: resource, | 					ProtectedResource: resource, | ||||||
| 					Scope:             in.scope, | 					Scope:             in.scope, | ||||||
| 				}) | 				}, options.ADFSOptions{}) | ||||||
| 
 | 
 | ||||||
| 				Expect(p.Data().Scope).To(Equal(in.expectedScope)) | 				Expect(p.Data().Scope).To(Equal(in.expectedScope)) | ||||||
| 				result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "") | 				result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "") | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/bitly/go-simplejson" | 	"github.com/bitly/go-simplejson" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
|  | @ -62,7 +63,7 @@ var ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewAzureProvider initiates a new AzureProvider
 | // NewAzureProvider initiates a new AzureProvider
 | ||||||
| func NewAzureProvider(p *ProviderData) *AzureProvider { | func NewAzureProvider(p *ProviderData, opts options.AzureOptions) *AzureProvider { | ||||||
| 	p.setProviderDefaults(providerDefaults{ | 	p.setProviderDefaults(providerDefaults{ | ||||||
| 		name:        azureProviderName, | 		name:        azureProviderName, | ||||||
| 		loginURL:    azureDefaultLoginURL, | 		loginURL:    azureDefaultLoginURL, | ||||||
|  | @ -80,25 +81,19 @@ func NewAzureProvider(p *ProviderData) *AzureProvider { | ||||||
| 	} | 	} | ||||||
| 	p.getAuthorizationHeaderFunc = makeAzureHeader | 	p.getAuthorizationHeaderFunc = makeAzureHeader | ||||||
| 
 | 
 | ||||||
| 	return &AzureProvider{ | 	tenant := "common" | ||||||
| 		ProviderData: p, | 	if opts.Tenant != "" { | ||||||
| 		Tenant:       "common", | 		tenant = opts.Tenant | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Configure defaults the AzureProvider configuration options
 |  | ||||||
| func (p *AzureProvider) Configure(tenant string) { |  | ||||||
| 	if tenant == "" || tenant == "common" { |  | ||||||
| 		// tenant is empty or default, remain on the default "common" tenant
 |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Specific tennant specified, override the Login and RedeemURLs
 |  | ||||||
| 	p.Tenant = tenant |  | ||||||
| 		overrideTenantURL(p.LoginURL, azureDefaultLoginURL, tenant, "authorize") | 		overrideTenantURL(p.LoginURL, azureDefaultLoginURL, tenant, "authorize") | ||||||
| 		overrideTenantURL(p.RedeemURL, azureDefaultRedeemURL, tenant, "token") | 		overrideTenantURL(p.RedeemURL, azureDefaultRedeemURL, tenant, "token") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	return &AzureProvider{ | ||||||
|  | 		ProviderData: p, | ||||||
|  | 		Tenant:       tenant, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func overrideTenantURL(current, defaultURL *url.URL, tenant, path string) { | func overrideTenantURL(current, defaultURL *url.URL, tenant, path string) { | ||||||
| 	if current == nil || current.String() == "" || current.String() == defaultURL.String() { | 	if current == nil || current.String() == "" || current.String() == defaultURL.String() { | ||||||
| 		*current = url.URL{ | 		*current = url.URL{ | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/coreos/go-oidc/v3/oidc" | 	"github.com/coreos/go-oidc/v3/oidc" | ||||||
| 	"github.com/golang-jwt/jwt" | 	"github.com/golang-jwt/jwt" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc" | 	internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc" | ||||||
| 
 | 
 | ||||||
|  | @ -39,7 +40,7 @@ type azureOAuthPayload struct { | ||||||
| 	IDToken      string `json:"id_token,omitempty"` | 	IDToken      string `json:"id_token,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func testAzureProvider(hostname string) *AzureProvider { | func testAzureProvider(hostname string, opts options.AzureOptions) *AzureProvider { | ||||||
| 	verificationOptions := &internaloidc.IDTokenVerificationOptions{ | 	verificationOptions := &internaloidc.IDTokenVerificationOptions{ | ||||||
| 		AudienceClaims: []string{"aud"}, | 		AudienceClaims: []string{"aud"}, | ||||||
| 		ClientID:       "cd6d4fae-f6a6-4a34-8454-2c6b598e9532", | 		ClientID:       "cd6d4fae-f6a6-4a34-8454-2c6b598e9532", | ||||||
|  | @ -64,7 +65,7 @@ func testAzureProvider(hostname string) *AzureProvider { | ||||||
| 					SkipExpiryCheck:   true, | 					SkipExpiryCheck:   true, | ||||||
| 				}, | 				}, | ||||||
| 			), verificationOptions), | 			), verificationOptions), | ||||||
| 		}) | 		}, opts) | ||||||
| 
 | 
 | ||||||
| 	if hostname != "" { | 	if hostname != "" { | ||||||
| 		updateURL(p.Data().LoginURL, hostname) | 		updateURL(p.Data().LoginURL, hostname) | ||||||
|  | @ -80,7 +81,7 @@ func TestNewAzureProvider(t *testing.T) { | ||||||
| 	g := NewWithT(t) | 	g := NewWithT(t) | ||||||
| 
 | 
 | ||||||
| 	// Test that defaults are set when calling for a new provider with nothing set
 | 	// Test that defaults are set when calling for a new provider with nothing set
 | ||||||
| 	providerData := NewAzureProvider(&ProviderData{}).Data() | 	providerData := NewAzureProvider(&ProviderData{}, options.AzureOptions{}).Data() | ||||||
| 	g.Expect(providerData.ProviderName).To(Equal("Azure")) | 	g.Expect(providerData.ProviderName).To(Equal("Azure")) | ||||||
| 	g.Expect(providerData.LoginURL.String()).To(Equal("https://login.microsoftonline.com/common/oauth2/authorize")) | 	g.Expect(providerData.LoginURL.String()).To(Equal("https://login.microsoftonline.com/common/oauth2/authorize")) | ||||||
| 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://login.microsoftonline.com/common/oauth2/token")) | 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://login.microsoftonline.com/common/oauth2/token")) | ||||||
|  | @ -111,7 +112,8 @@ func TestAzureProviderOverrides(t *testing.T) { | ||||||
| 			ProtectedResource: &url.URL{ | 			ProtectedResource: &url.URL{ | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
| 				Host:   "example.com"}, | 				Host:   "example.com"}, | ||||||
| 			Scope: "profile"}) | 			Scope: "profile"}, | ||||||
|  | 		options.AzureOptions{}) | ||||||
| 	assert.NotEqual(t, nil, p) | 	assert.NotEqual(t, nil, p) | ||||||
| 	assert.Equal(t, "Azure", p.Data().ProviderName) | 	assert.Equal(t, "Azure", p.Data().ProviderName) | ||||||
| 	assert.Equal(t, "https://example.com/oauth/auth", | 	assert.Equal(t, "https://example.com/oauth/auth", | ||||||
|  | @ -128,8 +130,7 @@ func TestAzureProviderOverrides(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestAzureSetTenant(t *testing.T) { | func TestAzureSetTenant(t *testing.T) { | ||||||
| 	p := testAzureProvider("") | 	p := testAzureProvider("", options.AzureOptions{Tenant: "example"}) | ||||||
| 	p.Configure("example") |  | ||||||
| 	assert.Equal(t, "Azure", p.Data().ProviderName) | 	assert.Equal(t, "Azure", p.Data().ProviderName) | ||||||
| 	assert.Equal(t, "example", p.Tenant) | 	assert.Equal(t, "example", p.Tenant) | ||||||
| 	assert.Equal(t, "https://login.microsoftonline.com/example/oauth2/authorize", | 	assert.Equal(t, "https://login.microsoftonline.com/example/oauth2/authorize", | ||||||
|  | @ -230,7 +231,7 @@ func TestAzureProviderEnrichSession(t *testing.T) { | ||||||
| 				bURL, _ := url.Parse(b.URL) | 				bURL, _ := url.Parse(b.URL) | ||||||
| 				host = bURL.Host | 				host = bURL.Host | ||||||
| 			} | 			} | ||||||
| 			p := testAzureProvider(host) | 			p := testAzureProvider(host, options.AzureOptions{}) | ||||||
| 			session := CreateAuthorizedSession() | 			session := CreateAuthorizedSession() | ||||||
| 			session.Email = testCase.Email | 			session.Email = testCase.Email | ||||||
| 			err := p.EnrichSession(context.Background(), session) | 			err := p.EnrichSession(context.Background(), session) | ||||||
|  | @ -323,7 +324,7 @@ func TestAzureProviderRedeem(t *testing.T) { | ||||||
| 			defer b.Close() | 			defer b.Close() | ||||||
| 
 | 
 | ||||||
| 			bURL, _ := url.Parse(b.URL) | 			bURL, _ := url.Parse(b.URL) | ||||||
| 			p := testAzureProvider(bURL.Host) | 			p := testAzureProvider(bURL.Host, options.AzureOptions{}) | ||||||
| 			p.Data().RedeemURL.Path = "/common/oauth2/token" | 			p.Data().RedeemURL.Path = "/common/oauth2/token" | ||||||
| 			s, err := p.Redeem(context.Background(), "https://localhost", "1234") | 			s, err := p.Redeem(context.Background(), "https://localhost", "1234") | ||||||
| 			if testCase.InjectRedeemURLError { | 			if testCase.InjectRedeemURLError { | ||||||
|  | @ -345,7 +346,7 @@ func TestAzureProviderRedeem(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestAzureProviderProtectedResourceConfigured(t *testing.T) { | func TestAzureProviderProtectedResourceConfigured(t *testing.T) { | ||||||
| 	p := testAzureProvider("") | 	p := testAzureProvider("", options.AzureOptions{}) | ||||||
| 	p.ProtectedResource, _ = url.Parse("http://my.resource.test") | 	p.ProtectedResource, _ = url.Parse("http://my.resource.test") | ||||||
| 	result := p.GetLoginURL("https://my.test.app/oauth", "", "") | 	result := p.GetLoginURL("https://my.test.app/oauth", "", "") | ||||||
| 	assert.Contains(t, result, "resource="+url.QueryEscape("http://my.resource.test")) | 	assert.Contains(t, result, "resource="+url.QueryEscape("http://my.resource.test")) | ||||||
|  | @ -381,7 +382,7 @@ func TestAzureProviderRefresh(t *testing.T) { | ||||||
| 	b := testAzureBackend(string(payloadBytes), newAccessToken, refreshToken) | 	b := testAzureBackend(string(payloadBytes), newAccessToken, refreshToken) | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testAzureProvider(bURL.Host) | 	p := testAzureProvider(bURL.Host, options.AzureOptions{}) | ||||||
| 
 | 
 | ||||||
| 	expires := time.Now().Add(time.Duration(-1) * time.Hour) | 	expires := time.Now().Add(time.Duration(-1) * time.Hour) | ||||||
| 	session := &sessions.SessionState{AccessToken: "some_access_token", RefreshToken: refreshToken, IDToken: "some_id_token", ExpiresOn: &expires} | 	session := &sessions.SessionState{AccessToken: "some_access_token", RefreshToken: refreshToken, IDToken: "some_id_token", ExpiresOn: &expires} | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
|  | @ -53,7 +54,7 @@ var ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewBitbucketProvider initiates a new BitbucketProvider
 | // NewBitbucketProvider initiates a new BitbucketProvider
 | ||||||
| func NewBitbucketProvider(p *ProviderData) *BitbucketProvider { | func NewBitbucketProvider(p *ProviderData, opts options.BitbucketOptions) *BitbucketProvider { | ||||||
| 	p.setProviderDefaults(providerDefaults{ | 	p.setProviderDefaults(providerDefaults{ | ||||||
| 		name:        bitbucketProviderName, | 		name:        bitbucketProviderName, | ||||||
| 		loginURL:    bitbucketDefaultLoginURL, | 		loginURL:    bitbucketDefaultLoginURL, | ||||||
|  | @ -62,19 +63,28 @@ func NewBitbucketProvider(p *ProviderData) *BitbucketProvider { | ||||||
| 		validateURL: bitbucketDefaultValidateURL, | 		validateURL: bitbucketDefaultValidateURL, | ||||||
| 		scope:       bitbucketDefaultScope, | 		scope:       bitbucketDefaultScope, | ||||||
| 	}) | 	}) | ||||||
| 	return &BitbucketProvider{ProviderData: p} | 
 | ||||||
|  | 	provider := &BitbucketProvider{ProviderData: p} | ||||||
|  | 
 | ||||||
|  | 	if opts.Team != "" { | ||||||
|  | 		provider.setTeam(opts.Team) | ||||||
|  | 	} | ||||||
|  | 	if opts.Repository != "" { | ||||||
|  | 		provider.setRepository(opts.Repository) | ||||||
|  | 	} | ||||||
|  | 	return provider | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetTeam defines the Bitbucket team the user must be part of
 | // setTeam defines the Bitbucket team the user must be part of
 | ||||||
| func (p *BitbucketProvider) SetTeam(team string) { | func (p *BitbucketProvider) setTeam(team string) { | ||||||
| 	p.Team = team | 	p.Team = team | ||||||
| 	if !strings.Contains(p.Scope, "team") { | 	if !strings.Contains(p.Scope, "team") { | ||||||
| 		p.Scope += " team" | 		p.Scope += " team" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetRepository defines the repository the user must have access to
 | // setRepository defines the repository the user must have access to
 | ||||||
| func (p *BitbucketProvider) SetRepository(repository string) { | func (p *BitbucketProvider) setRepository(repository string) { | ||||||
| 	p.Repository = repository | 	p.Repository = repository | ||||||
| 	if !strings.Contains(p.Scope, "repository") { | 	if !strings.Contains(p.Scope, "repository") { | ||||||
| 		p.Scope += " repository" | 		p.Scope += " repository" | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | @ -21,15 +22,12 @@ func testBitbucketProvider(hostname, team string, repository string) *BitbucketP | ||||||
| 			RedeemURL:    &url.URL{}, | 			RedeemURL:    &url.URL{}, | ||||||
| 			ProfileURL:   &url.URL{}, | 			ProfileURL:   &url.URL{}, | ||||||
| 			ValidateURL:  &url.URL{}, | 			ValidateURL:  &url.URL{}, | ||||||
| 			Scope:        ""}) | 			Scope:        ""}, | ||||||
| 
 | 		options.BitbucketOptions{ | ||||||
| 	if team != "" { | 			Team:       team, | ||||||
| 		p.SetTeam(team) | 			Repository: repository, | ||||||
| 	} | 		}, | ||||||
| 
 | 	) | ||||||
| 	if repository != "" { |  | ||||||
| 		p.SetRepository(repository) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if hostname != "" { | 	if hostname != "" { | ||||||
| 		updateURL(p.Data().LoginURL, hostname) | 		updateURL(p.Data().LoginURL, hostname) | ||||||
|  | @ -65,7 +63,7 @@ func TestNewBitbucketProvider(t *testing.T) { | ||||||
| 	g := NewWithT(t) | 	g := NewWithT(t) | ||||||
| 
 | 
 | ||||||
| 	// Test that defaults are set when calling for a new provider with nothing set
 | 	// Test that defaults are set when calling for a new provider with nothing set
 | ||||||
| 	providerData := NewBitbucketProvider(&ProviderData{}).Data() | 	providerData := NewBitbucketProvider(&ProviderData{}, options.BitbucketOptions{}).Data() | ||||||
| 	g.Expect(providerData.ProviderName).To(Equal("Bitbucket")) | 	g.Expect(providerData.ProviderName).To(Equal("Bitbucket")) | ||||||
| 	g.Expect(providerData.LoginURL.String()).To(Equal("https://bitbucket.org/site/oauth2/authorize")) | 	g.Expect(providerData.LoginURL.String()).To(Equal("https://bitbucket.org/site/oauth2/authorize")) | ||||||
| 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://bitbucket.org/site/oauth2/access_token")) | 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://bitbucket.org/site/oauth2/access_token")) | ||||||
|  | @ -101,7 +99,8 @@ func TestBitbucketProviderOverrides(t *testing.T) { | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
| 				Host:   "example.com", | 				Host:   "example.com", | ||||||
| 				Path:   "/api/v3/user"}, | 				Path:   "/api/v3/user"}, | ||||||
| 			Scope: "profile"}) | 			Scope: "profile"}, | ||||||
|  | 		options.BitbucketOptions{}) | ||||||
| 	assert.NotEqual(t, nil, p) | 	assert.NotEqual(t, nil, p) | ||||||
| 	assert.Equal(t, "Bitbucket", p.Data().ProviderName) | 	assert.Equal(t, "Bitbucket", p.Data().ProviderName) | ||||||
| 	assert.Equal(t, "https://example.com/oauth/auth", | 	assert.Equal(t, "https://example.com/oauth/auth", | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import ( | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
|  | @ -62,7 +63,7 @@ var ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewGitHubProvider initiates a new GitHubProvider
 | // NewGitHubProvider initiates a new GitHubProvider
 | ||||||
| func NewGitHubProvider(p *ProviderData) *GitHubProvider { | func NewGitHubProvider(p *ProviderData, opts options.GitHubOptions) *GitHubProvider { | ||||||
| 	p.setProviderDefaults(providerDefaults{ | 	p.setProviderDefaults(providerDefaults{ | ||||||
| 		name:        githubProviderName, | 		name:        githubProviderName, | ||||||
| 		loginURL:    githubDefaultLoginURL, | 		loginURL:    githubDefaultLoginURL, | ||||||
|  | @ -71,7 +72,13 @@ func NewGitHubProvider(p *ProviderData) *GitHubProvider { | ||||||
| 		validateURL: githubDefaultValidateURL, | 		validateURL: githubDefaultValidateURL, | ||||||
| 		scope:       githubDefaultScope, | 		scope:       githubDefaultScope, | ||||||
| 	}) | 	}) | ||||||
| 	return &GitHubProvider{ProviderData: p} | 
 | ||||||
|  | 	provider := &GitHubProvider{ProviderData: p} | ||||||
|  | 
 | ||||||
|  | 	provider.setOrgTeam(opts.Org, opts.Team) | ||||||
|  | 	provider.setRepo(opts.Repo, opts.Token) | ||||||
|  | 	provider.setUsers(opts.Users) | ||||||
|  | 	return provider | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func makeGitHubHeader(accessToken string) http.Header { | func makeGitHubHeader(accessToken string) http.Header { | ||||||
|  | @ -82,8 +89,8 @@ func makeGitHubHeader(accessToken string) http.Header { | ||||||
| 	return makeAuthorizationHeader(tokenTypeToken, accessToken, extraHeaders) | 	return makeAuthorizationHeader(tokenTypeToken, accessToken, extraHeaders) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetOrgTeam adds GitHub org reading parameters to the OAuth2 scope
 | // setOrgTeam adds GitHub org reading parameters to the OAuth2 scope
 | ||||||
| func (p *GitHubProvider) SetOrgTeam(org, team string) { | func (p *GitHubProvider) setOrgTeam(org, team string) { | ||||||
| 	p.Org = org | 	p.Org = org | ||||||
| 	p.Team = team | 	p.Team = team | ||||||
| 	if org != "" || team != "" { | 	if org != "" || team != "" { | ||||||
|  | @ -91,14 +98,14 @@ func (p *GitHubProvider) SetOrgTeam(org, team string) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetRepo configures the target repository and optional token to use
 | // setRepo configures the target repository and optional token to use
 | ||||||
| func (p *GitHubProvider) SetRepo(repo, token string) { | func (p *GitHubProvider) setRepo(repo, token string) { | ||||||
| 	p.Repo = repo | 	p.Repo = repo | ||||||
| 	p.Token = token | 	p.Token = token | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetUsers configures allowed usernames
 | // setUsers configures allowed usernames
 | ||||||
| func (p *GitHubProvider) SetUsers(users []string) { | func (p *GitHubProvider) setUsers(users []string) { | ||||||
| 	p.Users = users | 	p.Users = users | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,12 +7,13 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func testGitHubProvider(hostname string) *GitHubProvider { | func testGitHubProvider(hostname string, opts options.GitHubOptions) *GitHubProvider { | ||||||
| 	p := NewGitHubProvider( | 	p := NewGitHubProvider( | ||||||
| 		&ProviderData{ | 		&ProviderData{ | ||||||
| 			ProviderName: "", | 			ProviderName: "", | ||||||
|  | @ -20,7 +21,8 @@ func testGitHubProvider(hostname string) *GitHubProvider { | ||||||
| 			RedeemURL:    &url.URL{}, | 			RedeemURL:    &url.URL{}, | ||||||
| 			ProfileURL:   &url.URL{}, | 			ProfileURL:   &url.URL{}, | ||||||
| 			ValidateURL:  &url.URL{}, | 			ValidateURL:  &url.URL{}, | ||||||
| 			Scope:        ""}) | 			Scope:        ""}, | ||||||
|  | 		opts) | ||||||
| 	if hostname != "" { | 	if hostname != "" { | ||||||
| 		updateURL(p.Data().LoginURL, hostname) | 		updateURL(p.Data().LoginURL, hostname) | ||||||
| 		updateURL(p.Data().RedeemURL, hostname) | 		updateURL(p.Data().RedeemURL, hostname) | ||||||
|  | @ -71,7 +73,7 @@ func TestNewGitHubProvider(t *testing.T) { | ||||||
| 	g := NewWithT(t) | 	g := NewWithT(t) | ||||||
| 
 | 
 | ||||||
| 	// Test that defaults are set when calling for a new provider with nothing set
 | 	// Test that defaults are set when calling for a new provider with nothing set
 | ||||||
| 	providerData := NewGitHubProvider(&ProviderData{}).Data() | 	providerData := NewGitHubProvider(&ProviderData{}, options.GitHubOptions{}).Data() | ||||||
| 	g.Expect(providerData.ProviderName).To(Equal("GitHub")) | 	g.Expect(providerData.ProviderName).To(Equal("GitHub")) | ||||||
| 	g.Expect(providerData.LoginURL.String()).To(Equal("https://github.com/login/oauth/authorize")) | 	g.Expect(providerData.LoginURL.String()).To(Equal("https://github.com/login/oauth/authorize")) | ||||||
| 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://github.com/login/oauth/access_token")) | 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://github.com/login/oauth/access_token")) | ||||||
|  | @ -95,7 +97,8 @@ func TestGitHubProviderOverrides(t *testing.T) { | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
| 				Host:   "api.example.com", | 				Host:   "api.example.com", | ||||||
| 				Path:   "/"}, | 				Path:   "/"}, | ||||||
| 			Scope: "profile"}) | 			Scope: "profile"}, | ||||||
|  | 		options.GitHubOptions{}) | ||||||
| 	assert.NotEqual(t, nil, p) | 	assert.NotEqual(t, nil, p) | ||||||
| 	assert.Equal(t, "GitHub", p.Data().ProviderName) | 	assert.Equal(t, "GitHub", p.Data().ProviderName) | ||||||
| 	assert.Equal(t, "https://example.com/login/oauth/authorize", | 	assert.Equal(t, "https://example.com/login/oauth/authorize", | ||||||
|  | @ -114,7 +117,7 @@ func TestGitHubProvider_getEmail(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, options.GitHubOptions{}) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -129,7 +132,7 @@ func TestGitHubProvider_getEmailNotVerified(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, options.GitHubOptions{}) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -149,8 +152,11 @@ func TestGitHubProvider_getEmailWithOrg(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.Org = "testorg1" | 		options.GitHubOptions{ | ||||||
|  | 			Org: "testorg1", | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -166,8 +172,12 @@ func TestGitHubProvider_getEmailWithWriteAccessToPublicRepo(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetRepo("oauth2-proxy/oauth2-proxy", "") | 		options.GitHubOptions{ | ||||||
|  | 			Repo:  "oauth2-proxy/oauth2-proxy", | ||||||
|  | 			Token: "token", | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -183,8 +193,12 @@ func TestGitHubProvider_getEmailWithReadOnlyAccessToPrivateRepo(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetRepo("oauth2-proxy/oauth2-proxy", "") | 		options.GitHubOptions{ | ||||||
|  | 			Repo:  "oauth2-proxy/oauth2-proxy", | ||||||
|  | 			Token: "token", | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -200,8 +214,12 @@ func TestGitHubProvider_getEmailWithWriteAccessToPrivateRepo(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetRepo("oauth2-proxy/oauth2-proxy", "") | 		options.GitHubOptions{ | ||||||
|  | 			Repo:  "oauth2-proxy/oauth2-proxy", | ||||||
|  | 			Token: "token", | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -216,8 +234,11 @@ func TestGitHubProvider_getEmailWithNoAccessToPrivateRepo(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetRepo("oauth2-proxy/oauth2-proxy", "") | 		options.GitHubOptions{ | ||||||
|  | 			Repo: "oauth2-proxy/oauth2-proxy", | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -232,8 +253,12 @@ func TestGitHubProvider_getEmailWithToken(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetRepo("oauth2-proxy/oauth2-proxy", "token") | 		options.GitHubOptions{ | ||||||
|  | 			Repo:  "oauth2-proxy/oauth2-proxy", | ||||||
|  | 			Token: "token", | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -248,7 +273,7 @@ func TestGitHubProvider_getEmailFailedRequest(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, options.GitHubOptions{}) | ||||||
| 
 | 
 | ||||||
| 	// We'll trigger a request failure by using an unexpected access
 | 	// We'll trigger a request failure by using an unexpected access
 | ||||||
| 	// token. Alternatively, we could allow the parsing of the payload as
 | 	// token. Alternatively, we could allow the parsing of the payload as
 | ||||||
|  | @ -266,7 +291,7 @@ func TestGitHubProvider_getEmailNotPresentInPayload(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, options.GitHubOptions{}) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -281,7 +306,7 @@ func TestGitHubProvider_getUser(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, options.GitHubOptions{}) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getUser(context.Background(), session) | 	err := p.getUser(context.Background(), session) | ||||||
|  | @ -297,8 +322,12 @@ func TestGitHubProvider_getUserWithRepoAndToken(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetRepo("oauth2-proxy/oauth2-proxy", "token") | 		options.GitHubOptions{ | ||||||
|  | 			Repo:  "oauth2-proxy/oauth2-proxy", | ||||||
|  | 			Token: "token", | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getUser(context.Background(), session) | 	err := p.getUser(context.Background(), session) | ||||||
|  | @ -311,8 +340,12 @@ func TestGitHubProvider_getUserWithRepoAndTokenWithoutPushAccess(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetRepo("oauth2-proxy/oauth2-proxy", "token") | 		options.GitHubOptions{ | ||||||
|  | 			Repo:  "oauth2-proxy/oauth2-proxy", | ||||||
|  | 			Token: "token", | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getUser(context.Background(), session) | 	err := p.getUser(context.Background(), session) | ||||||
|  | @ -328,8 +361,11 @@ func TestGitHubProvider_getEmailWithUsername(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetUsers([]string{"mbland", "octocat"}) | 		options.GitHubOptions{ | ||||||
|  | 			Users: []string{"mbland", "octocat"}, | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -345,8 +381,11 @@ func TestGitHubProvider_getEmailWithNotAllowedUsername(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetUsers([]string{"octocat"}) | 		options.GitHubOptions{ | ||||||
|  | 			Users: []string{"octocat"}, | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -366,9 +405,12 @@ func TestGitHubProvider_getEmailWithUsernameAndNotBelongToOrg(t *testing.T) { | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetOrgTeam("not_belong_to", "") | 		options.GitHubOptions{ | ||||||
| 	p.SetUsers([]string{"mbland"}) | 			Org:   "not_belog_to", | ||||||
|  | 			Users: []string{"mbland"}, | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  | @ -385,9 +427,13 @@ func TestGitHubProvider_getEmailWithUsernameAndNoAccessToPrivateRepo(t *testing. | ||||||
| 	defer b.Close() | 	defer b.Close() | ||||||
| 
 | 
 | ||||||
| 	bURL, _ := url.Parse(b.URL) | 	bURL, _ := url.Parse(b.URL) | ||||||
| 	p := testGitHubProvider(bURL.Host) | 	p := testGitHubProvider(bURL.Host, | ||||||
| 	p.SetRepo("oauth2-proxy/oauth2-proxy", "") | 		options.GitHubOptions{ | ||||||
| 	p.SetUsers([]string{"mbland"}) | 			Repo:  "oauth2-proxy/oauth2-proxy", | ||||||
|  | 			Token: "token", | ||||||
|  | 			Users: []string{"mbland"}, | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	session := CreateAuthorizedSession() | 	session := CreateAuthorizedSession() | ||||||
| 	err := p.getEmail(context.Background(), session) | 	err := p.getEmail(context.Background(), session) | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import ( | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
|  | @ -30,7 +31,7 @@ type GitLabProvider struct { | ||||||
| var _ Provider = (*GitLabProvider)(nil) | var _ Provider = (*GitLabProvider)(nil) | ||||||
| 
 | 
 | ||||||
| // NewGitLabProvider initiates a new GitLabProvider
 | // NewGitLabProvider initiates a new GitLabProvider
 | ||||||
| func NewGitLabProvider(p *ProviderData) *GitLabProvider { | func NewGitLabProvider(p *ProviderData, opts options.GitLabOptions) (*GitLabProvider, error) { | ||||||
| 	p.ProviderName = gitlabProviderName | 	p.ProviderName = gitlabProviderName | ||||||
| 	if p.Scope == "" { | 	if p.Scope == "" { | ||||||
| 		p.Scope = gitlabDefaultScope | 		p.Scope = gitlabDefaultScope | ||||||
|  | @ -41,15 +42,22 @@ func NewGitLabProvider(p *ProviderData) *GitLabProvider { | ||||||
| 		SkipNonce:    false, | 		SkipNonce:    false, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &GitLabProvider{ | 	provider := &GitLabProvider{ | ||||||
| 		OIDCProvider:    oidcProvider, | 		OIDCProvider:    oidcProvider, | ||||||
| 		oidcRefreshFunc: oidcProvider.RefreshSession, | 		oidcRefreshFunc: oidcProvider.RefreshSession, | ||||||
| 	} | 	} | ||||||
|  | 	provider.setAllowedGroups(opts.Group) | ||||||
|  | 
 | ||||||
|  | 	if err := provider.setAllowedProjects(opts.Projects); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("could not configure allowed projects: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| // SetAllowedProjects adds Gitlab projects to the AllowedGroups list
 | 	return provider, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // setAllowedProjects adds Gitlab projects to the AllowedGroups list
 | ||||||
| // and tracks them to do a project API lookup during `EnrichSession`.
 | // and tracks them to do a project API lookup during `EnrichSession`.
 | ||||||
| func (p *GitLabProvider) SetAllowedProjects(projects []string) error { | func (p *GitLabProvider) setAllowedProjects(projects []string) error { | ||||||
| 	for _, project := range projects { | 	for _, project := range projects { | ||||||
| 		gp, err := newGitlabProject(project) | 		gp, err := newGitlabProject(project) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  |  | ||||||
|  | @ -7,21 +7,26 @@ import ( | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
| 	. "github.com/onsi/ginkgo/extensions/table" | 	. "github.com/onsi/ginkgo/extensions/table" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func testGitLabProvider(hostname string) *GitLabProvider { | func testGitLabProvider(hostname, scope string, opts options.GitLabOptions) (*GitLabProvider, error) { | ||||||
| 	p := NewGitLabProvider( | 	p, err := NewGitLabProvider( | ||||||
| 		&ProviderData{ | 		&ProviderData{ | ||||||
| 			ProviderName: "", | 			ProviderName: "", | ||||||
| 			LoginURL:     &url.URL{}, | 			LoginURL:     &url.URL{}, | ||||||
| 			RedeemURL:    &url.URL{}, | 			RedeemURL:    &url.URL{}, | ||||||
| 			ProfileURL:   &url.URL{}, | 			ProfileURL:   &url.URL{}, | ||||||
| 			ValidateURL:  &url.URL{}, | 			ValidateURL:  &url.URL{}, | ||||||
| 			Scope:        ""}) | 			Scope:        scope}, | ||||||
|  | 		opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
| 	if hostname != "" { | 	if hostname != "" { | ||||||
| 		updateURL(p.Data().LoginURL, hostname) | 		updateURL(p.Data().LoginURL, hostname) | ||||||
| 		updateURL(p.Data().RedeemURL, hostname) | 		updateURL(p.Data().RedeemURL, hostname) | ||||||
|  | @ -29,7 +34,7 @@ func testGitLabProvider(hostname string) *GitLabProvider { | ||||||
| 		updateURL(p.Data().ValidateURL, hostname) | 		updateURL(p.Data().ValidateURL, hostname) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return p | 	return p, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func testGitLabBackend() *httptest.Server { | func testGitLabBackend() *httptest.Server { | ||||||
|  | @ -157,7 +162,8 @@ var _ = Describe("Gitlab Provider Tests", func() { | ||||||
| 		bURL, err := url.Parse(b.URL) | 		bURL, err := url.Parse(b.URL) | ||||||
| 		Expect(err).To(BeNil()) | 		Expect(err).To(BeNil()) | ||||||
| 
 | 
 | ||||||
| 		p = testGitLabProvider(bURL.Host) | 		p, err = testGitLabProvider(bURL.Host, "", options.GitLabOptions{}) | ||||||
|  | 		Expect(err).ToNot(HaveOccurred()) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	AfterEach(func() { | 	AfterEach(func() { | ||||||
|  | @ -219,21 +225,23 @@ var _ = Describe("Gitlab Provider Tests", func() { | ||||||
| 
 | 
 | ||||||
| 		DescribeTable("should return expected results", | 		DescribeTable("should return expected results", | ||||||
| 			func(in entitiesTableInput) { | 			func(in entitiesTableInput) { | ||||||
| 				p.AllowUnverifiedEmail = true | 				bURL, err := url.Parse(b.URL) | ||||||
| 				if in.scope != "" { | 				Expect(err).To(BeNil()) | ||||||
| 					p.Scope = in.scope |  | ||||||
| 				} |  | ||||||
| 				session := &sessions.SessionState{AccessToken: "gitlab_access_token"} |  | ||||||
| 
 | 
 | ||||||
| 				p.SetAllowedGroups(in.allowedGroups) | 				p, err := testGitLabProvider(bURL.Host, in.scope, options.GitLabOptions{ | ||||||
| 
 | 					Group:    in.allowedGroups, | ||||||
| 				err := p.SetAllowedProjects(in.allowedProjects) | 					Projects: in.allowedProjects, | ||||||
|  | 				}) | ||||||
| 				if in.expectedError == nil { | 				if in.expectedError == nil { | ||||||
| 					Expect(err).To(BeNil()) | 					Expect(err).To(BeNil()) | ||||||
| 				} else { | 				} else { | ||||||
| 					Expect(err).To(MatchError(in.expectedError)) | 					Expect(err).To(MatchError(in.expectedError)) | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
|  | 
 | ||||||
|  | 				p.AllowUnverifiedEmail = true | ||||||
|  | 				session := &sessions.SessionState{AccessToken: "gitlab_access_token"} | ||||||
|  | 
 | ||||||
| 				Expect(p.Scope).To(Equal(in.expectedScope)) | 				Expect(p.Scope).To(Equal(in.expectedScope)) | ||||||
| 
 | 
 | ||||||
| 				err = p.EnrichSession(context.Background(), session) | 				err = p.EnrichSession(context.Background(), session) | ||||||
|  | @ -302,7 +310,7 @@ var _ = Describe("Gitlab Provider Tests", func() { | ||||||
| 			}), | 			}), | ||||||
| 			Entry("invalid project format", entitiesTableInput{ | 			Entry("invalid project format", entitiesTableInput{ | ||||||
| 				allowedProjects: []string{"my_group/my_invalid_project=123"}, | 				allowedProjects: []string{"my_group/my_invalid_project=123"}, | ||||||
| 				expectedError:   errors.New("invalid gitlab project access level specified (my_group/my_invalid_project)"), | 				expectedError:   errors.New("could not configure allowed projects: invalid gitlab project access level specified (my_group/my_invalid_project)"), | ||||||
| 				expectedScope:   "openid email read_api", | 				expectedScope:   "openid email read_api", | ||||||
| 			}), | 			}), | ||||||
| 		) | 		) | ||||||
|  |  | ||||||
|  | @ -10,9 +10,11 @@ import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
|  | @ -79,7 +81,7 @@ var ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewGoogleProvider initiates a new GoogleProvider
 | // NewGoogleProvider initiates a new GoogleProvider
 | ||||||
| func NewGoogleProvider(p *ProviderData) *GoogleProvider { | func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProvider, error) { | ||||||
| 	p.setProviderDefaults(providerDefaults{ | 	p.setProviderDefaults(providerDefaults{ | ||||||
| 		name:        googleProviderName, | 		name:        googleProviderName, | ||||||
| 		loginURL:    googleDefaultLoginURL, | 		loginURL:    googleDefaultLoginURL, | ||||||
|  | @ -88,7 +90,7 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider { | ||||||
| 		validateURL: googleDefaultValidateURL, | 		validateURL: googleDefaultValidateURL, | ||||||
| 		scope:       googleDefaultScope, | 		scope:       googleDefaultScope, | ||||||
| 	}) | 	}) | ||||||
| 	return &GoogleProvider{ | 	provider := &GoogleProvider{ | ||||||
| 		ProviderData: p, | 		ProviderData: p, | ||||||
| 		// Set a default groupValidator to just always return valid (true), it will
 | 		// Set a default groupValidator to just always return valid (true), it will
 | ||||||
| 		// be overwritten if we configured a Google group restriction.
 | 		// be overwritten if we configured a Google group restriction.
 | ||||||
|  | @ -96,6 +98,21 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider { | ||||||
| 			return true | 			return true | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if opts.ServiceAccountJSON != "" { | ||||||
|  | 		file, err := os.Open(opts.ServiceAccountJSON) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("invalid Google credentials file: %s", opts.ServiceAccountJSON) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Backwards compatibility with `--google-group` option
 | ||||||
|  | 		if len(opts.Groups) > 0 { | ||||||
|  | 			provider.setAllowedGroups(opts.Groups) | ||||||
|  | 		} | ||||||
|  | 		provider.setGroupRestriction(opts.Groups, opts.AdminEmail, file) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return provider, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func claimsFromIDToken(idToken string) (*claims, error) { | func claimsFromIDToken(idToken string) (*claims, error) { | ||||||
|  | @ -195,7 +212,7 @@ func (p *GoogleProvider) EnrichSession(_ context.Context, s *sessions.SessionSta | ||||||
| // account credentials.
 | // account credentials.
 | ||||||
| //
 | //
 | ||||||
| // TODO (@NickMeves) - Unit Test this OR refactor away from groupValidator func
 | // TODO (@NickMeves) - Unit Test this OR refactor away from groupValidator func
 | ||||||
| func (p *GoogleProvider) SetGroupRestriction(groups []string, adminEmail string, credentialsReader io.Reader) { | func (p *GoogleProvider) setGroupRestriction(groups []string, adminEmail string, credentialsReader io.Reader) { | ||||||
| 	adminService := getAdminService(adminEmail, credentialsReader) | 	adminService := getAdminService(adminEmail, credentialsReader) | ||||||
| 	p.groupValidator = func(s *sessions.SessionState) bool { | 	p.groupValidator = func(s *sessions.SessionState) bool { | ||||||
| 		// Reset our saved Groups in case membership changed
 | 		// Reset our saved Groups in case membership changed
 | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | @ -25,22 +26,29 @@ func newRedeemServer(body []byte) (*url.URL, *httptest.Server) { | ||||||
| 	return u, s | 	return u, s | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newGoogleProvider() *GoogleProvider { | func newGoogleProvider(t *testing.T) *GoogleProvider { | ||||||
| 	return NewGoogleProvider( | 	g := NewWithT(t) | ||||||
|  | 	p, err := NewGoogleProvider( | ||||||
| 		&ProviderData{ | 		&ProviderData{ | ||||||
| 			ProviderName: "", | 			ProviderName: "", | ||||||
| 			LoginURL:     &url.URL{}, | 			LoginURL:     &url.URL{}, | ||||||
| 			RedeemURL:    &url.URL{}, | 			RedeemURL:    &url.URL{}, | ||||||
| 			ProfileURL:   &url.URL{}, | 			ProfileURL:   &url.URL{}, | ||||||
| 			ValidateURL:  &url.URL{}, | 			ValidateURL:  &url.URL{}, | ||||||
| 			Scope:        ""}) | 			Scope:        ""}, | ||||||
|  | 		options.GoogleOptions{}) | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 	return p | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewGoogleProvider(t *testing.T) { | func TestNewGoogleProvider(t *testing.T) { | ||||||
| 	g := NewWithT(t) | 	g := NewWithT(t) | ||||||
| 
 | 
 | ||||||
| 	// Test that defaults are set when calling for a new provider with nothing set
 | 	// Test that defaults are set when calling for a new provider with nothing set
 | ||||||
| 	providerData := NewGoogleProvider(&ProviderData{}).Data() | 	provider, err := NewGoogleProvider(&ProviderData{}, options.GoogleOptions{}) | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 	providerData := provider.Data() | ||||||
|  | 
 | ||||||
| 	g.Expect(providerData.ProviderName).To(Equal("Google")) | 	g.Expect(providerData.ProviderName).To(Equal("Google")) | ||||||
| 	g.Expect(providerData.LoginURL.String()).To(Equal("https://accounts.google.com/o/oauth2/auth?access_type=offline")) | 	g.Expect(providerData.LoginURL.String()).To(Equal("https://accounts.google.com/o/oauth2/auth?access_type=offline")) | ||||||
| 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://www.googleapis.com/oauth2/v3/token")) | 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://www.googleapis.com/oauth2/v3/token")) | ||||||
|  | @ -50,7 +58,7 @@ func TestNewGoogleProvider(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGoogleProviderOverrides(t *testing.T) { | func TestGoogleProviderOverrides(t *testing.T) { | ||||||
| 	p := NewGoogleProvider( | 	p, err := NewGoogleProvider( | ||||||
| 		&ProviderData{ | 		&ProviderData{ | ||||||
| 			LoginURL: &url.URL{ | 			LoginURL: &url.URL{ | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
|  | @ -68,7 +76,9 @@ func TestGoogleProviderOverrides(t *testing.T) { | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
| 				Host:   "example.com", | 				Host:   "example.com", | ||||||
| 				Path:   "/oauth/tokeninfo"}, | 				Path:   "/oauth/tokeninfo"}, | ||||||
| 			Scope: "profile"}) | 			Scope: "profile"}, | ||||||
|  | 		options.GoogleOptions{}) | ||||||
|  | 	assert.NoError(t, err) | ||||||
| 	assert.NotEqual(t, nil, p) | 	assert.NotEqual(t, nil, p) | ||||||
| 	assert.Equal(t, "Google", p.Data().ProviderName) | 	assert.Equal(t, "Google", p.Data().ProviderName) | ||||||
| 	assert.Equal(t, "https://example.com/oauth/auth", | 	assert.Equal(t, "https://example.com/oauth/auth", | ||||||
|  | @ -90,7 +100,7 @@ type redeemResponse struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGoogleProviderGetEmailAddress(t *testing.T) { | func TestGoogleProviderGetEmailAddress(t *testing.T) { | ||||||
| 	p := newGoogleProvider() | 	p := newGoogleProvider(t) | ||||||
| 	body, err := json.Marshal(redeemResponse{ | 	body, err := json.Marshal(redeemResponse{ | ||||||
| 		AccessToken:  "a1234", | 		AccessToken:  "a1234", | ||||||
| 		ExpiresIn:    10, | 		ExpiresIn:    10, | ||||||
|  | @ -147,7 +157,7 @@ func TestGoogleProviderGroupValidator(t *testing.T) { | ||||||
| 	for name, tc := range testCases { | 	for name, tc := range testCases { | ||||||
| 		t.Run(name, func(t *testing.T) { | 		t.Run(name, func(t *testing.T) { | ||||||
| 			g := NewWithT(t) | 			g := NewWithT(t) | ||||||
| 			p := newGoogleProvider() | 			p := newGoogleProvider(t) | ||||||
| 			if tc.validatorFunc != nil { | 			if tc.validatorFunc != nil { | ||||||
| 				p.groupValidator = tc.validatorFunc | 				p.groupValidator = tc.validatorFunc | ||||||
| 			} | 			} | ||||||
|  | @ -158,7 +168,7 @@ func TestGoogleProviderGroupValidator(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| //
 | //
 | ||||||
| func TestGoogleProviderGetEmailAddressInvalidEncoding(t *testing.T) { | func TestGoogleProviderGetEmailAddressInvalidEncoding(t *testing.T) { | ||||||
| 	p := newGoogleProvider() | 	p := newGoogleProvider(t) | ||||||
| 	body, err := json.Marshal(redeemResponse{ | 	body, err := json.Marshal(redeemResponse{ | ||||||
| 		AccessToken: "a1234", | 		AccessToken: "a1234", | ||||||
| 		IDToken:     "ignored prefix." + `{"email": "michael.bland@gsa.gov"}`, | 		IDToken:     "ignored prefix." + `{"email": "michael.bland@gsa.gov"}`, | ||||||
|  | @ -176,7 +186,7 @@ func TestGoogleProviderGetEmailAddressInvalidEncoding(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGoogleProviderRedeemFailsNoCLientSecret(t *testing.T) { | func TestGoogleProviderRedeemFailsNoCLientSecret(t *testing.T) { | ||||||
| 	p := newGoogleProvider() | 	p := newGoogleProvider(t) | ||||||
| 	p.ProviderData.ClientSecretFile = "srvnoerre" | 	p.ProviderData.ClientSecretFile = "srvnoerre" | ||||||
| 
 | 
 | ||||||
| 	session, err := p.Redeem(context.Background(), "http://redirect/", "code1234") | 	session, err := p.Redeem(context.Background(), "http://redirect/", "code1234") | ||||||
|  | @ -188,7 +198,7 @@ func TestGoogleProviderRedeemFailsNoCLientSecret(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGoogleProviderGetEmailAddressInvalidJson(t *testing.T) { | func TestGoogleProviderGetEmailAddressInvalidJson(t *testing.T) { | ||||||
| 	p := newGoogleProvider() | 	p := newGoogleProvider(t) | ||||||
| 
 | 
 | ||||||
| 	body, err := json.Marshal(redeemResponse{ | 	body, err := json.Marshal(redeemResponse{ | ||||||
| 		AccessToken: "a1234", | 		AccessToken: "a1234", | ||||||
|  | @ -208,7 +218,7 @@ func TestGoogleProviderGetEmailAddressInvalidJson(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGoogleProviderGetEmailAddressEmailMissing(t *testing.T) { | func TestGoogleProviderGetEmailAddressEmailMissing(t *testing.T) { | ||||||
| 	p := newGoogleProvider() | 	p := newGoogleProvider(t) | ||||||
| 	body, err := json.Marshal(redeemResponse{ | 	body, err := json.Marshal(redeemResponse{ | ||||||
| 		AccessToken: "a1234", | 		AccessToken: "a1234", | ||||||
| 		IDToken:     "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"not_email": "missing"}`)), | 		IDToken:     "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"not_email": "missing"}`)), | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
|  | @ -48,7 +49,7 @@ var ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewKeycloakProvider creates a KeyCloakProvider using the passed ProviderData
 | // NewKeycloakProvider creates a KeyCloakProvider using the passed ProviderData
 | ||||||
| func NewKeycloakProvider(p *ProviderData) *KeycloakProvider { | func NewKeycloakProvider(p *ProviderData, opts options.KeycloakOptions) *KeycloakProvider { | ||||||
| 	p.setProviderDefaults(providerDefaults{ | 	p.setProviderDefaults(providerDefaults{ | ||||||
| 		name:        keycloakProviderName, | 		name:        keycloakProviderName, | ||||||
| 		loginURL:    keycloakDefaultLoginURL, | 		loginURL:    keycloakDefaultLoginURL, | ||||||
|  | @ -57,7 +58,10 @@ func NewKeycloakProvider(p *ProviderData) *KeycloakProvider { | ||||||
| 		validateURL: keycloakDefaultValidateURL, | 		validateURL: keycloakDefaultValidateURL, | ||||||
| 		scope:       keycloakDefaultScope, | 		scope:       keycloakDefaultScope, | ||||||
| 	}) | 	}) | ||||||
| 	return &KeycloakProvider{ProviderData: p} | 
 | ||||||
|  | 	provider := &KeycloakProvider{ProviderData: p} | ||||||
|  | 	provider.setAllowedGroups(opts.Groups) | ||||||
|  | 	return provider | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // EnrichSession uses the Keycloak userinfo endpoint to populate the session's
 | // EnrichSession uses the Keycloak userinfo endpoint to populate the session's
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -15,21 +16,25 @@ type KeycloakOIDCProvider struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewKeycloakOIDCProvider makes a KeycloakOIDCProvider using the ProviderData
 | // NewKeycloakOIDCProvider makes a KeycloakOIDCProvider using the ProviderData
 | ||||||
| func NewKeycloakOIDCProvider(p *ProviderData) *KeycloakOIDCProvider { | func NewKeycloakOIDCProvider(p *ProviderData, opts options.KeycloakOptions) *KeycloakOIDCProvider { | ||||||
| 	p.ProviderName = keycloakOIDCProviderName | 	p.ProviderName = keycloakOIDCProviderName | ||||||
| 	return &KeycloakOIDCProvider{ | 
 | ||||||
|  | 	provider := &KeycloakOIDCProvider{ | ||||||
| 		OIDCProvider: &OIDCProvider{ | 		OIDCProvider: &OIDCProvider{ | ||||||
| 			ProviderData: p, | 			ProviderData: p, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	provider.addAllowedRoles(opts.Roles) | ||||||
|  | 	return provider | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var _ Provider = (*KeycloakOIDCProvider)(nil) | var _ Provider = (*KeycloakOIDCProvider)(nil) | ||||||
| 
 | 
 | ||||||
| // AddAllowedRoles sets Keycloak roles that are authorized.
 | // addAllowedRoles sets Keycloak roles that are authorized.
 | ||||||
| // Assumes `SetAllowedGroups` is already called on groups and appends to that
 | // Assumes `SetAllowedGroups` is already called on groups and appends to that
 | ||||||
| // with `role:` prefixed roles.
 | // with `role:` prefixed roles.
 | ||||||
| func (p *KeycloakOIDCProvider) AddAllowedRoles(roles []string) { | func (p *KeycloakOIDCProvider) addAllowedRoles(roles []string) { | ||||||
| 	if p.AllowedGroups == nil { | 	if p.AllowedGroups == nil { | ||||||
| 		p.AllowedGroups = make(map[string]struct{}) | 		p.AllowedGroups = make(map[string]struct{}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/coreos/go-oidc/v3/oidc" | 	"github.com/coreos/go-oidc/v3/oidc" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
|  | @ -39,11 +40,11 @@ func getAccessToken() string { | ||||||
| 
 | 
 | ||||||
| func newTestKeycloakOIDCSetup() (*httptest.Server, *KeycloakOIDCProvider) { | func newTestKeycloakOIDCSetup() (*httptest.Server, *KeycloakOIDCProvider) { | ||||||
| 	redeemURL, server := newOIDCServer([]byte(fmt.Sprintf(`{"email": "new@thing.com", "expires_in": 300, "access_token": "%v"}`, getAccessToken()))) | 	redeemURL, server := newOIDCServer([]byte(fmt.Sprintf(`{"email": "new@thing.com", "expires_in": 300, "access_token": "%v"}`, getAccessToken()))) | ||||||
| 	provider := newKeycloakOIDCProvider(redeemURL) | 	provider := newKeycloakOIDCProvider(redeemURL, options.KeycloakOptions{}) | ||||||
| 	return server, provider | 	return server, provider | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider { | func newKeycloakOIDCProvider(serverURL *url.URL, opts options.KeycloakOptions) *KeycloakOIDCProvider { | ||||||
| 	verificationOptions := &internaloidc.IDTokenVerificationOptions{ | 	verificationOptions := &internaloidc.IDTokenVerificationOptions{ | ||||||
| 		AudienceClaims: []string{defaultAudienceClaim}, | 		AudienceClaims: []string{defaultAudienceClaim}, | ||||||
| 		ClientID:       mockClientID, | 		ClientID:       mockClientID, | ||||||
|  | @ -66,7 +67,8 @@ func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider { | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
| 				Host:   "keycloak-oidc.com", | 				Host:   "keycloak-oidc.com", | ||||||
| 				Path:   "/api/v3/user"}, | 				Path:   "/api/v3/user"}, | ||||||
| 			Scope: "openid email profile"}) | 			Scope: "openid email profile"}, | ||||||
|  | 		opts) | ||||||
| 
 | 
 | ||||||
| 	if serverURL != nil { | 	if serverURL != nil { | ||||||
| 		p.RedeemURL.Scheme = serverURL.Scheme | 		p.RedeemURL.Scheme = serverURL.Scheme | ||||||
|  | @ -88,7 +90,7 @@ func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider { | ||||||
| var _ = Describe("Keycloak OIDC Provider Tests", func() { | var _ = Describe("Keycloak OIDC Provider Tests", func() { | ||||||
| 	Context("New Provider Init", func() { | 	Context("New Provider Init", func() { | ||||||
| 		It("creates new keycloak oidc provider with expected defaults", func() { | 		It("creates new keycloak oidc provider with expected defaults", func() { | ||||||
| 			p := newKeycloakOIDCProvider(nil) | 			p := newKeycloakOIDCProvider(nil, options.KeycloakOptions{}) | ||||||
| 			providerData := p.Data() | 			providerData := p.Data() | ||||||
| 			Expect(providerData.ProviderName).To(Equal(keycloakOIDCProviderName)) | 			Expect(providerData.ProviderName).To(Equal(keycloakOIDCProviderName)) | ||||||
| 			Expect(providerData.LoginURL.String()).To(Equal("https://keycloak-oidc.com/oauth/auth")) | 			Expect(providerData.LoginURL.String()).To(Equal("https://keycloak-oidc.com/oauth/auth")) | ||||||
|  | @ -101,8 +103,9 @@ var _ = Describe("Keycloak OIDC Provider Tests", func() { | ||||||
| 
 | 
 | ||||||
| 	Context("Allowed Roles", func() { | 	Context("Allowed Roles", func() { | ||||||
| 		It("should prefix allowed roles and add them to groups", func() { | 		It("should prefix allowed roles and add them to groups", func() { | ||||||
| 			p := newKeycloakOIDCProvider(nil) | 			p := newKeycloakOIDCProvider(nil, options.KeycloakOptions{ | ||||||
| 			p.AddAllowedRoles([]string{"admin", "editor"}) | 				Roles: []string{"admin", "editor"}, | ||||||
|  | 			}) | ||||||
| 			Expect(p.AllowedGroups).To(HaveKey("role:admin")) | 			Expect(p.AllowedGroups).To(HaveKey("role:admin")) | ||||||
| 			Expect(p.AllowedGroups).To(HaveKey("role:editor")) | 			Expect(p.AllowedGroups).To(HaveKey("role:editor")) | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
| 	. "github.com/onsi/ginkgo/extensions/table" | 	. "github.com/onsi/ginkgo/extensions/table" | ||||||
|  | @ -27,7 +28,8 @@ func testKeycloakProvider(backend *httptest.Server) (*KeycloakProvider, error) { | ||||||
| 			RedeemURL:    &url.URL{}, | 			RedeemURL:    &url.URL{}, | ||||||
| 			ProfileURL:   &url.URL{}, | 			ProfileURL:   &url.URL{}, | ||||||
| 			ValidateURL:  &url.URL{}, | 			ValidateURL:  &url.URL{}, | ||||||
| 			Scope:        ""}) | 			Scope:        ""}, | ||||||
|  | 		options.KeycloakOptions{}) | ||||||
| 
 | 
 | ||||||
| 	if backend != nil { | 	if backend != nil { | ||||||
| 		bURL, err := url.Parse(backend.URL) | 		bURL, err := url.Parse(backend.URL) | ||||||
|  | @ -48,7 +50,7 @@ func testKeycloakProvider(backend *httptest.Server) (*KeycloakProvider, error) { | ||||||
| var _ = Describe("Keycloak Provider Tests", func() { | var _ = Describe("Keycloak Provider Tests", func() { | ||||||
| 	Context("New Provider Init", func() { | 	Context("New Provider Init", func() { | ||||||
| 		It("uses defaults", func() { | 		It("uses defaults", func() { | ||||||
| 			providerData := NewKeycloakProvider(&ProviderData{}).Data() | 			providerData := NewKeycloakProvider(&ProviderData{}, options.KeycloakOptions{}).Data() | ||||||
| 			Expect(providerData.ProviderName).To(Equal("Keycloak")) | 			Expect(providerData.ProviderName).To(Equal("Keycloak")) | ||||||
| 			Expect(providerData.LoginURL.String()).To(Equal("https://keycloak.org/oauth/authorize")) | 			Expect(providerData.LoginURL.String()).To(Equal("https://keycloak.org/oauth/authorize")) | ||||||
| 			Expect(providerData.RedeemURL.String()).To(Equal("https://keycloak.org/oauth/token")) | 			Expect(providerData.RedeemURL.String()).To(Equal("https://keycloak.org/oauth/token")) | ||||||
|  | @ -76,7 +78,8 @@ var _ = Describe("Keycloak Provider Tests", func() { | ||||||
| 						Scheme: "https", | 						Scheme: "https", | ||||||
| 						Host:   "example.com", | 						Host:   "example.com", | ||||||
| 						Path:   "/api/v3/user"}, | 						Path:   "/api/v3/user"}, | ||||||
| 					Scope: "profile"}) | 					Scope: "profile"}, | ||||||
|  | 				options.KeycloakOptions{}) | ||||||
| 			providerData := p.Data() | 			providerData := p.Data() | ||||||
| 
 | 
 | ||||||
| 			Expect(providerData.ProviderName).To(Equal("Keycloak")) | 			Expect(providerData.ProviderName).To(Equal("Keycloak")) | ||||||
|  |  | ||||||
|  | @ -5,12 +5,15 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| 	"crypto/rsa" | 	"crypto/rsa" | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
| 	"math/big" | 	"math/big" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/golang-jwt/jwt" | 	"github.com/golang-jwt/jwt" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
| 	"gopkg.in/square/go-jose.v2" | 	"gopkg.in/square/go-jose.v2" | ||||||
|  | @ -78,7 +81,7 @@ var ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewLoginGovProvider initiates a new LoginGovProvider
 | // NewLoginGovProvider initiates a new LoginGovProvider
 | ||||||
| func NewLoginGovProvider(p *ProviderData) *LoginGovProvider { | func NewLoginGovProvider(p *ProviderData, opts options.LoginGovOptions) (*LoginGovProvider, error) { | ||||||
| 	p.setProviderDefaults(providerDefaults{ | 	p.setProviderDefaults(providerDefaults{ | ||||||
| 		name:        loginGovProviderName, | 		name:        loginGovProviderName, | ||||||
| 		loginURL:    loginGovDefaultLoginURL, | 		loginURL:    loginGovDefaultLoginURL, | ||||||
|  | @ -87,10 +90,50 @@ func NewLoginGovProvider(p *ProviderData) *LoginGovProvider { | ||||||
| 		validateURL: loginGovDefaultProfileURL, | 		validateURL: loginGovDefaultProfileURL, | ||||||
| 		scope:       loginGovDefaultScope, | 		scope:       loginGovDefaultScope, | ||||||
| 	}) | 	}) | ||||||
| 	return &LoginGovProvider{ | 	provider := &LoginGovProvider{ | ||||||
| 		ProviderData: p, | 		ProviderData: p, | ||||||
| 		Nonce:        randSeq(32), | 		Nonce:        randSeq(32), | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := provider.configure(opts); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("could not configure login.gov provider: %v", err) | ||||||
|  | 	} | ||||||
|  | 	return provider, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *LoginGovProvider) configure(opts options.LoginGovOptions) error { | ||||||
|  | 	pubJWKURL, err := url.Parse(opts.PubJWKURL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("could not parse Public JWK URL: %v", err) | ||||||
|  | 	} | ||||||
|  | 	p.PubJWKURL = pubJWKURL | ||||||
|  | 
 | ||||||
|  | 	// JWT key can be supplied via env variable or file in the filesystem, but not both.
 | ||||||
|  | 	switch { | ||||||
|  | 	case opts.JWTKey != "" && opts.JWTKeyFile != "": | ||||||
|  | 		return errors.New("cannot set both jwt-key and jwt-key-file options") | ||||||
|  | 	case opts.JWTKey == "" && opts.JWTKeyFile == "": | ||||||
|  | 		return errors.New("login.gov provider requires a private key for signing JWTs") | ||||||
|  | 	case opts.JWTKey != "": | ||||||
|  | 		// The JWT Key is in the commandline argument
 | ||||||
|  | 		signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(opts.JWTKey)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("could not parse RSA Private Key PEM: %v", err) | ||||||
|  | 		} | ||||||
|  | 		p.JWTKey = signKey | ||||||
|  | 	case opts.JWTKeyFile != "": | ||||||
|  | 		// The JWT key is in the filesystem
 | ||||||
|  | 		keyData, err := ioutil.ReadFile(opts.JWTKeyFile) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("could not read key file: %v", opts.JWTKeyFile) | ||||||
|  | 		} | ||||||
|  | 		signKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("could not parse private key from PEM file: %v", opts.JWTKeyFile) | ||||||
|  | 		} | ||||||
|  | 		p.JWTKey = signKey | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type loginGovCustomClaims struct { | type loginGovCustomClaims struct { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| package providers | package providers | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"crypto" | 	"crypto" | ||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| 	"crypto/rsa" | 	"crypto/rsa" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"encoding/pem" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | @ -13,6 +16,7 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/golang-jwt/jwt" | 	"github.com/golang-jwt/jwt" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"gopkg.in/square/go-jose.v2" | 	"gopkg.in/square/go-jose.v2" | ||||||
|  | @ -32,12 +36,35 @@ func newLoginGovServer(body []byte) (*url.URL, *httptest.Server) { | ||||||
| 	return u, s | 	return u, s | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newLoginGovProvider() (l *LoginGovProvider, serverKey *MyKeyData, err error) { | func newPrivateKeyBytes() ([]byte, error) { | ||||||
|  | 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	keyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	privateKeyBlock := &pem.Block{ | ||||||
|  | 		Type:  "RSA PRIVATE KEY", | ||||||
|  | 		Bytes: keyBytes, | ||||||
|  | 	} | ||||||
|  | 	b := &bytes.Buffer{} | ||||||
|  | 	if err := pem.Encode(b, privateKeyBlock); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return b.Bytes(), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newLoginGovProvider() (*LoginGovProvider, *MyKeyData, error) { | ||||||
| 	key, err := rsa.GenerateKey(rand.Reader, 2048) | 	key, err := rsa.GenerateKey(rand.Reader, 2048) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return nil, nil, err | ||||||
| 	} | 	} | ||||||
| 	serverKey = &MyKeyData{ | 	serverKey := &MyKeyData{ | ||||||
| 		PubKey:  key.Public(), | 		PubKey:  key.Public(), | ||||||
| 		PrivKey: key, | 		PrivKey: key, | ||||||
| 		PubJWK: jose.JSONWebKey{ | 		PubJWK: jose.JSONWebKey{ | ||||||
|  | @ -48,29 +75,40 @@ func newLoginGovProvider() (l *LoginGovProvider, serverKey *MyKeyData, err error | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048) | 	privKey, err := newPrivateKeyBytes() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return nil, nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	l = NewLoginGovProvider( | 	l, err := NewLoginGovProvider( | ||||||
| 		&ProviderData{ | 		&ProviderData{ | ||||||
| 			ProviderName: "", | 			ProviderName: "", | ||||||
| 			LoginURL:     &url.URL{}, | 			LoginURL:     &url.URL{}, | ||||||
| 			RedeemURL:    &url.URL{}, | 			RedeemURL:    &url.URL{}, | ||||||
| 			ProfileURL:   &url.URL{}, | 			ProfileURL:   &url.URL{}, | ||||||
| 			ValidateURL:  &url.URL{}, | 			ValidateURL:  &url.URL{}, | ||||||
| 			Scope:        ""}) | 			Scope:        ""}, | ||||||
| 	l.JWTKey = privateKey | 		options.LoginGovOptions{ | ||||||
|  | 			JWTKey: string(privKey), | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| 	l.Nonce = "fakenonce" | 	l.Nonce = "fakenonce" | ||||||
| 	return | 	return l, serverKey, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewLoginGovProvider(t *testing.T) { | func TestNewLoginGovProvider(t *testing.T) { | ||||||
| 	g := NewWithT(t) | 	g := NewWithT(t) | ||||||
| 
 | 
 | ||||||
|  | 	privKey, err := newPrivateKeyBytes() | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
| 	// Test that defaults are set when calling for a new provider with nothing set
 | 	// Test that defaults are set when calling for a new provider with nothing set
 | ||||||
| 	providerData := NewLoginGovProvider(&ProviderData{}).Data() | 	provider, err := NewLoginGovProvider(&ProviderData{}, options.LoginGovOptions{ | ||||||
|  | 		JWTKey: string(privKey), | ||||||
|  | 	}) | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 	providerData := provider.Data() | ||||||
| 	g.Expect(providerData.ProviderName).To(Equal("login.gov")) | 	g.Expect(providerData.ProviderName).To(Equal("login.gov")) | ||||||
| 	g.Expect(providerData.LoginURL.String()).To(Equal("https://secure.login.gov/openid_connect/authorize")) | 	g.Expect(providerData.LoginURL.String()).To(Equal("https://secure.login.gov/openid_connect/authorize")) | ||||||
| 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://secure.login.gov/api/openid_connect/token")) | 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://secure.login.gov/api/openid_connect/token")) | ||||||
|  | @ -80,7 +118,10 @@ func TestNewLoginGovProvider(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestLoginGovProviderOverrides(t *testing.T) { | func TestLoginGovProviderOverrides(t *testing.T) { | ||||||
| 	p := NewLoginGovProvider( | 	privKey, err := newPrivateKeyBytes() | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 	p, err := NewLoginGovProvider( | ||||||
| 		&ProviderData{ | 		&ProviderData{ | ||||||
| 			LoginURL: &url.URL{ | 			LoginURL: &url.URL{ | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
|  | @ -94,7 +135,11 @@ func TestLoginGovProviderOverrides(t *testing.T) { | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
| 				Host:   "example.com", | 				Host:   "example.com", | ||||||
| 				Path:   "/oauth/profile"}, | 				Path:   "/oauth/profile"}, | ||||||
| 			Scope: "profile"}) | 			Scope: "profile"}, | ||||||
|  | 		options.LoginGovOptions{ | ||||||
|  | 			JWTKey: string(privKey), | ||||||
|  | 		}) | ||||||
|  | 	assert.NoError(t, err) | ||||||
| 	assert.NotEqual(t, nil, p) | 	assert.NotEqual(t, nil, p) | ||||||
| 	assert.Equal(t, "login.gov", p.Data().ProviderName) | 	assert.Equal(t, "login.gov", p.Data().ProviderName) | ||||||
| 	assert.Equal(t, "https://example.com/oauth/auth", | 	assert.Equal(t, "https://example.com/oauth/auth", | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| package providers | package providers | ||||||
| 
 | 
 | ||||||
|  | import "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
|  | 
 | ||||||
| // NextcloudProvider represents an Nextcloud based Identity Provider
 | // NextcloudProvider represents an Nextcloud based Identity Provider
 | ||||||
| type NextcloudProvider struct { | type NextcloudProvider struct { | ||||||
| 	*ProviderData | 	*ProviderData | ||||||
|  | @ -13,7 +15,7 @@ const nextCloudProviderName = "Nextcloud" | ||||||
| func NewNextcloudProvider(p *ProviderData) *NextcloudProvider { | func NewNextcloudProvider(p *ProviderData) *NextcloudProvider { | ||||||
| 	p.ProviderName = nextCloudProviderName | 	p.ProviderName = nextCloudProviderName | ||||||
| 	p.getAuthorizationHeaderFunc = makeOIDCHeader | 	p.getAuthorizationHeaderFunc = makeOIDCHeader | ||||||
| 	if p.EmailClaim == OIDCEmailClaim { | 	if p.EmailClaim == options.OIDCEmailClaim { | ||||||
| 		// This implies the email claim has not been overridden, we should set a default
 | 		// This implies the email claim has not been overridden, we should set a default
 | ||||||
| 		// for this provider
 | 		// for this provider
 | ||||||
| 		p.EmailClaim = "ocs.data.email" | 		p.EmailClaim = "ocs.data.email" | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	"golang.org/x/oauth2" | 	"golang.org/x/oauth2" | ||||||
|  | @ -20,13 +21,13 @@ type OIDCProvider struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewOIDCProvider initiates a new OIDCProvider
 | // NewOIDCProvider initiates a new OIDCProvider
 | ||||||
| func NewOIDCProvider(p *ProviderData) *OIDCProvider { | func NewOIDCProvider(p *ProviderData, opts options.OIDCOptions) *OIDCProvider { | ||||||
| 	p.ProviderName = "OpenID Connect" | 	p.ProviderName = "OpenID Connect" | ||||||
| 	p.getAuthorizationHeaderFunc = makeOIDCHeader | 	p.getAuthorizationHeaderFunc = makeOIDCHeader | ||||||
| 
 | 
 | ||||||
| 	return &OIDCProvider{ | 	return &OIDCProvider{ | ||||||
| 		ProviderData: p, | 		ProviderData: p, | ||||||
| 		SkipNonce:    true, | 		SkipNonce:    opts.InsecureSkipNonce, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/coreos/go-oidc/v3/oidc" | 	"github.com/coreos/go-oidc/v3/oidc" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption" | ||||||
| 	internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc" | 	internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc" | ||||||
|  | @ -25,7 +26,7 @@ type redeemTokenResponse struct { | ||||||
| 	IDToken      string `json:"id_token,omitempty"` | 	IDToken      string `json:"id_token,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newOIDCProvider(serverURL *url.URL) *OIDCProvider { | func newOIDCProvider(serverURL *url.URL, skipNonce bool) *OIDCProvider { | ||||||
| 	verificationOptions := &internaloidc.IDTokenVerificationOptions{ | 	verificationOptions := &internaloidc.IDTokenVerificationOptions{ | ||||||
| 		AudienceClaims: []string{"aud"}, | 		AudienceClaims: []string{"aud"}, | ||||||
| 		ClientID:       "https://test.myapp.com", | 		ClientID:       "https://test.myapp.com", | ||||||
|  | @ -61,7 +62,9 @@ func newOIDCProvider(serverURL *url.URL) *OIDCProvider { | ||||||
| 		), verificationOptions), | 		), verificationOptions), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	p := NewOIDCProvider(providerData) | 	p := NewOIDCProvider(providerData, options.OIDCOptions{ | ||||||
|  | 		InsecureSkipNonce: skipNonce, | ||||||
|  | 	}) | ||||||
| 
 | 
 | ||||||
| 	return p | 	return p | ||||||
| } | } | ||||||
|  | @ -77,7 +80,7 @@ func newOIDCServer(body []byte) (*url.URL, *httptest.Server) { | ||||||
| 
 | 
 | ||||||
| func newTestOIDCSetup(body []byte) (*httptest.Server, *OIDCProvider) { | func newTestOIDCSetup(body []byte) (*httptest.Server, *OIDCProvider) { | ||||||
| 	redeemURL, server := newOIDCServer(body) | 	redeemURL, server := newOIDCServer(body) | ||||||
| 	provider := newOIDCProvider(redeemURL) | 	provider := newOIDCProvider(redeemURL, false) | ||||||
| 	return server, provider | 	return server, provider | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -86,7 +89,7 @@ func TestOIDCProviderGetLoginURL(t *testing.T) { | ||||||
| 		Scheme: "https", | 		Scheme: "https", | ||||||
| 		Host:   "oauth2proxy.oidctest", | 		Host:   "oauth2proxy.oidctest", | ||||||
| 	} | 	} | ||||||
| 	provider := newOIDCProvider(serverURL) | 	provider := newOIDCProvider(serverURL, true) | ||||||
| 
 | 
 | ||||||
| 	n, err := encryption.Nonce() | 	n, err := encryption.Nonce() | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/coreos/go-oidc/v3/oidc" | 	"github.com/coreos/go-oidc/v3/oidc" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc" | 	internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc" | ||||||
|  | @ -18,14 +19,10 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	OIDCEmailClaim  = "email" |  | ||||||
| 	OIDCGroupsClaim = "groups" |  | ||||||
| 	// This is not exported as it's not currently user configurable
 | 	// This is not exported as it's not currently user configurable
 | ||||||
| 	oidcUserClaim = "sub" | 	oidcUserClaim = "sub" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var OIDCAudienceClaims = []string{"aud"} |  | ||||||
| 
 |  | ||||||
| // ProviderData contains information required to configure all implementations
 | // ProviderData contains information required to configure all implementations
 | ||||||
| // of OAuth2 providers
 | // of OAuth2 providers
 | ||||||
| type ProviderData struct { | type ProviderData struct { | ||||||
|  | @ -76,9 +73,9 @@ func (p *ProviderData) GetClientSecret() (clientSecret string, err error) { | ||||||
| 	return string(fileClientSecret), nil | 	return string(fileClientSecret), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetAllowedGroups organizes a group list into the AllowedGroups map
 | // setAllowedGroups organizes a group list into the AllowedGroups map
 | ||||||
| // to be consumed by Authorize implementations
 | // to be consumed by Authorize implementations
 | ||||||
| func (p *ProviderData) SetAllowedGroups(groups []string) { | func (p *ProviderData) setAllowedGroups(groups []string) { | ||||||
| 	p.AllowedGroups = make(map[string]struct{}, len(groups)) | 	p.AllowedGroups = make(map[string]struct{}, len(groups)) | ||||||
| 	for _, group := range groups { | 	for _, group := range groups { | ||||||
| 		p.AllowedGroups[group] = struct{}{} | 		p.AllowedGroups[group] = struct{}{} | ||||||
|  | @ -172,7 +169,7 @@ func (p *ProviderData) buildSessionFromClaims(rawIDToken, accessToken string) (* | ||||||
| 
 | 
 | ||||||
| 	// `email_verified` must be present and explicitly set to `false` to be
 | 	// `email_verified` must be present and explicitly set to `false` to be
 | ||||||
| 	// considered unverified.
 | 	// considered unverified.
 | ||||||
| 	verifyEmail := (p.EmailClaim == OIDCEmailClaim) && !p.AllowUnverifiedEmail | 	verifyEmail := (p.EmailClaim == options.OIDCEmailClaim) && !p.AllowUnverifiedEmail | ||||||
| 
 | 
 | ||||||
| 	var verified bool | 	var verified bool | ||||||
| 	exists, err := extractor.GetClaimInto("email_verified", &verified) | 	exists, err := extractor.GetClaimInto("email_verified", &verified) | ||||||
|  |  | ||||||
|  | @ -107,7 +107,7 @@ func TestProviderDataAuthorize(t *testing.T) { | ||||||
| 				Groups: tc.groups, | 				Groups: tc.groups, | ||||||
| 			} | 			} | ||||||
| 			p := &ProviderData{} | 			p := &ProviderData{} | ||||||
| 			p.SetAllowedGroups(tc.allowedGroups) | 			p.setAllowedGroups(tc.allowedGroups) | ||||||
| 
 | 
 | ||||||
| 			authorized, err := p.Authorize(context.Background(), session) | 			authorized, err := p.Authorize(context.Background(), session) | ||||||
| 			g.Expect(err).ToNot(HaveOccurred()) | 			g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  |  | ||||||
|  | @ -2,8 +2,18 @@ package providers | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/coreos/go-oidc/v3/oidc" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
|  | 	internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests" | ||||||
|  | 	k8serrors "k8s.io/apimachinery/pkg/util/errors" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Provider represents an upstream identity provider implementation
 | // Provider represents an upstream identity provider implementation
 | ||||||
|  | @ -20,38 +30,247 @@ type Provider interface { | ||||||
| 	CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) | 	CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New provides a new Provider based on the configured provider string
 | func NewProvider(providerConfig options.Provider) (Provider, error) { | ||||||
| func New(provider string, p *ProviderData) Provider { | 	providerData, err := newProviderDataFromConfig(providerConfig) | ||||||
| 	switch provider { | 	if err != nil { | ||||||
| 	case "linkedin": | 		return nil, fmt.Errorf("could not create provider data: %v", err) | ||||||
| 		return NewLinkedInProvider(p) | 	} | ||||||
| 	case "facebook": | 	switch providerConfig.Type { | ||||||
| 		return NewFacebookProvider(p) | 	case options.ADFSProvider: | ||||||
| 	case "github": | 		return NewADFSProvider(providerData, providerConfig.ADFSConfig), nil | ||||||
| 		return NewGitHubProvider(p) | 	case options.AzureProvider: | ||||||
| 	case "keycloak": | 		return NewAzureProvider(providerData, providerConfig.AzureConfig), nil | ||||||
| 		return NewKeycloakProvider(p) | 	case options.BitbucketProvider: | ||||||
| 	case "keycloak-oidc": | 		return NewBitbucketProvider(providerData, providerConfig.BitbucketConfig), nil | ||||||
| 		return NewKeycloakOIDCProvider(p) | 	case options.DigitalOceanProvider: | ||||||
| 	case "azure": | 		return NewDigitalOceanProvider(providerData), nil | ||||||
| 		return NewAzureProvider(p) | 	case options.FacebookProvider: | ||||||
| 	case "adfs": | 		return NewFacebookProvider(providerData), nil | ||||||
| 		return NewADFSProvider(p) | 	case options.GitHubProvider: | ||||||
| 	case "gitlab": | 		return NewGitHubProvider(providerData, providerConfig.GitHubConfig), nil | ||||||
| 		return NewGitLabProvider(p) | 	case options.GitLabProvider: | ||||||
| 	case "oidc": | 		return NewGitLabProvider(providerData, providerConfig.GitLabConfig) | ||||||
| 		return NewOIDCProvider(p) | 	case options.GoogleProvider: | ||||||
| 	case "login.gov": | 		return NewGoogleProvider(providerData, providerConfig.GoogleConfig) | ||||||
| 		return NewLoginGovProvider(p) | 	case options.KeycloakProvider: | ||||||
| 	case "bitbucket": | 		return NewKeycloakProvider(providerData, providerConfig.KeycloakConfig), nil | ||||||
| 		return NewBitbucketProvider(p) | 	case options.KeycloakOIDCProvider: | ||||||
| 	case "nextcloud": | 		return NewKeycloakOIDCProvider(providerData, providerConfig.KeycloakConfig), nil | ||||||
| 		return NewNextcloudProvider(p) | 	case options.LinkedInProvider: | ||||||
| 	case "digitalocean": | 		return NewLinkedInProvider(providerData), nil | ||||||
| 		return NewDigitalOceanProvider(p) | 	case options.LoginGovProvider: | ||||||
| 	case "google": | 		return NewLoginGovProvider(providerData, providerConfig.LoginGovConfig) | ||||||
| 		return NewGoogleProvider(p) | 	case options.NextCloudProvider: | ||||||
|  | 		return NewNextcloudProvider(providerData), nil | ||||||
|  | 	case options.OIDCProvider: | ||||||
|  | 		return NewOIDCProvider(providerData, providerConfig.OIDCConfig), nil | ||||||
| 	default: | 	default: | ||||||
| 		return nil | 		return nil, fmt.Errorf("unknown provider type %q", providerConfig.Type) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData, error) { | ||||||
|  | 	p := &ProviderData{ | ||||||
|  | 		Scope:            providerConfig.Scope, | ||||||
|  | 		ClientID:         providerConfig.ClientID, | ||||||
|  | 		ClientSecret:     providerConfig.ClientSecret, | ||||||
|  | 		ClientSecretFile: providerConfig.ClientSecretFile, | ||||||
|  | 		Prompt:           providerConfig.Prompt, | ||||||
|  | 		ApprovalPrompt:   providerConfig.ApprovalPrompt, | ||||||
|  | 		AcrValues:        providerConfig.AcrValues, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	needsVerifier, err := providerRequiresOIDCProviderVerifier(providerConfig.Type) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if needsVerifier { | ||||||
|  | 		oidcProvider, verifier, err := newOIDCProviderVerifier(providerConfig) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("error setting OIDC configuration: %v", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		p.Verifier = verifier | ||||||
|  | 		if oidcProvider != nil { | ||||||
|  | 			// Use the discovered values rather than any specified values
 | ||||||
|  | 			providerConfig.LoginURL = oidcProvider.Endpoint().AuthURL | ||||||
|  | 			providerConfig.RedeemURL = oidcProvider.Endpoint().TokenURL | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	errs := []error{} | ||||||
|  | 	for name, u := range map[string]struct { | ||||||
|  | 		dst *url.URL | ||||||
|  | 		raw string | ||||||
|  | 	}{ | ||||||
|  | 		"login":    {dst: p.LoginURL, raw: providerConfig.LoginURL}, | ||||||
|  | 		"redeem":   {dst: p.RedeemURL, raw: providerConfig.RedeemURL}, | ||||||
|  | 		"profile":  {dst: p.ProfileURL, raw: providerConfig.ProfileURL}, | ||||||
|  | 		"validate": {dst: p.ValidateURL, raw: providerConfig.ValidateURL}, | ||||||
|  | 		"resource": {dst: p.ProtectedResource, raw: providerConfig.ProtectedResource}, | ||||||
|  | 	} { | ||||||
|  | 		var err error | ||||||
|  | 		u.dst, err = url.Parse(u.raw) | ||||||
|  | 		if err != nil { | ||||||
|  | 			errs = append(errs, fmt.Errorf("could not parse %s URL: %v", name, err)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(errs) > 0 { | ||||||
|  | 		return nil, k8serrors.NewAggregate(errs) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Make the OIDC options available to all providers that support it
 | ||||||
|  | 	p.AllowUnverifiedEmail = providerConfig.OIDCConfig.InsecureAllowUnverifiedEmail | ||||||
|  | 	p.EmailClaim = providerConfig.OIDCConfig.EmailClaim | ||||||
|  | 	p.GroupsClaim = providerConfig.OIDCConfig.GroupsClaim | ||||||
|  | 
 | ||||||
|  | 	// TODO (@NickMeves) - Remove This
 | ||||||
|  | 	// Backwards Compatibility for Deprecated UserIDClaim option
 | ||||||
|  | 	if providerConfig.OIDCConfig.EmailClaim == options.OIDCEmailClaim && | ||||||
|  | 		providerConfig.OIDCConfig.UserIDClaim != options.OIDCEmailClaim { | ||||||
|  | 		p.EmailClaim = providerConfig.OIDCConfig.UserIDClaim | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if providerConfig.Scope == "" { | ||||||
|  | 		providerConfig.Scope = "openid email profile" | ||||||
|  | 
 | ||||||
|  | 		if len(providerConfig.AllowedGroups) > 0 { | ||||||
|  | 			providerConfig.Scope += " groups" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if providerConfig.OIDCConfig.UserIDClaim == "" { | ||||||
|  | 		providerConfig.OIDCConfig.UserIDClaim = "email" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p.setAllowedGroups(providerConfig.AllowedGroups) | ||||||
|  | 
 | ||||||
|  | 	return p, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func providerRequiresOIDCProviderVerifier(providerType options.ProviderType) (bool, error) { | ||||||
|  | 	switch providerType { | ||||||
|  | 	case options.BitbucketProvider, options.DigitalOceanProvider, options.FacebookProvider, options.GitHubProvider, | ||||||
|  | 		options.GoogleProvider, options.KeycloakProvider, options.LinkedInProvider, options.LoginGovProvider, options.NextCloudProvider: | ||||||
|  | 		return false, nil | ||||||
|  | 	case options.ADFSProvider, options.AzureProvider, options.GitLabProvider, options.KeycloakOIDCProvider, options.OIDCProvider: | ||||||
|  | 		return true, nil | ||||||
|  | 	default: | ||||||
|  | 		return false, fmt.Errorf("unknown provider type: %s", providerType) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newOIDCProviderVerifier(providerConfig options.Provider) (*oidc.Provider, *internaloidc.IDTokenVerifier, error) { | ||||||
|  | 	// If the issuer isn't set, default it for platforms where it makes sense
 | ||||||
|  | 	if providerConfig.OIDCConfig.IssuerURL == "" { | ||||||
|  | 		switch providerConfig.Type { | ||||||
|  | 		case "gitlab": | ||||||
|  | 			providerConfig.OIDCConfig.IssuerURL = "https://gitlab.com" | ||||||
|  | 		case "oidc": | ||||||
|  | 			return nil, nil, errors.New("missing required setting: OIDC Issuer URL cannot be empty") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch { | ||||||
|  | 	case providerConfig.OIDCConfig.InsecureSkipIssuerVerification && !providerConfig.OIDCConfig.SkipDiscovery: | ||||||
|  | 		verifier, err := newInsecureSkipIssuerVerificationOIDCVerifier(providerConfig) | ||||||
|  | 		return nil, verifier, err | ||||||
|  | 	case providerConfig.OIDCConfig.SkipDiscovery: | ||||||
|  | 		verifier, err := newSkipDiscoveryOIDCVerifier(providerConfig) | ||||||
|  | 		return nil, verifier, err | ||||||
|  | 	default: | ||||||
|  | 		return newDiscoveryOIDCProviderVerifier(providerConfig) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newDiscoveryOIDCProviderVerifier(providerConfig options.Provider) (*oidc.Provider, *internaloidc.IDTokenVerifier, error) { | ||||||
|  | 	// Configure discoverable provider data.
 | ||||||
|  | 	provider, err := oidc.NewProvider(context.TODO(), providerConfig.OIDCConfig.IssuerURL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 	verificationOptions := &internaloidc.IDTokenVerificationOptions{ | ||||||
|  | 		AudienceClaims: providerConfig.OIDCConfig.AudienceClaims, | ||||||
|  | 		ClientID:       providerConfig.ClientID, | ||||||
|  | 		ExtraAudiences: providerConfig.OIDCConfig.ExtraAudiences, | ||||||
|  | 	} | ||||||
|  | 	verifier := internaloidc.NewVerifier(provider.Verifier(&oidc.Config{ | ||||||
|  | 		ClientID:          providerConfig.ClientID, | ||||||
|  | 		SkipIssuerCheck:   providerConfig.OIDCConfig.InsecureSkipIssuerVerification, | ||||||
|  | 		SkipClientIDCheck: true, // client id check is done within oauth2-proxy: IDTokenVerifier.Verify
 | ||||||
|  | 	}), verificationOptions) | ||||||
|  | 
 | ||||||
|  | 	return provider, verifier, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newInsecureSkipIssuerVerificationOIDCVerifier(providerConfig options.Provider) (*internaloidc.IDTokenVerifier, error) { | ||||||
|  | 	// go-oidc doesn't let us pass bypass the issuer check this in the oidc.NewProvider call
 | ||||||
|  | 	// (which uses discovery to get the URLs), so we'll do a quick check ourselves and if
 | ||||||
|  | 	// we get the URLs, we'll just use the non-discovery path.
 | ||||||
|  | 
 | ||||||
|  | 	logger.Printf("Performing OIDC Discovery...") | ||||||
|  | 
 | ||||||
|  | 	requestURL := strings.TrimSuffix(providerConfig.OIDCConfig.IssuerURL, "/") + "/.well-known/openid-configuration" | ||||||
|  | 	body, err := requests.New(requestURL). | ||||||
|  | 		Do(). | ||||||
|  | 		UnmarshalJSON() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to discover OIDC configuration: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Prefer manually configured URLs. It's a bit unclear
 | ||||||
|  | 	// why you'd be doing discovery and also providing the URLs
 | ||||||
|  | 	// explicitly though...
 | ||||||
|  | 	if providerConfig.LoginURL == "" { | ||||||
|  | 		providerConfig.LoginURL = body.Get("authorization_endpoint").MustString() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if providerConfig.RedeemURL == "" { | ||||||
|  | 		providerConfig.RedeemURL = body.Get("token_endpoint").MustString() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if providerConfig.OIDCConfig.JwksURL == "" { | ||||||
|  | 		providerConfig.OIDCConfig.JwksURL = body.Get("jwks_uri").MustString() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if providerConfig.ProfileURL == "" { | ||||||
|  | 		providerConfig.ProfileURL = body.Get("userinfo_endpoint").MustString() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Now we have performed the discovery, construct the verifier manually
 | ||||||
|  | 	return newSkipDiscoveryOIDCVerifier(providerConfig) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newSkipDiscoveryOIDCVerifier(providerConfig options.Provider) (*internaloidc.IDTokenVerifier, error) { | ||||||
|  | 	var errs []error | ||||||
|  | 
 | ||||||
|  | 	// Construct a manual IDTokenVerifier from issuer URL & JWKS URI
 | ||||||
|  | 	// instead of metadata discovery if we enable -skip-oidc-discovery.
 | ||||||
|  | 	// In this case we need to make sure the required endpoints for
 | ||||||
|  | 	// the provider are configured.
 | ||||||
|  | 	if providerConfig.LoginURL == "" { | ||||||
|  | 		errs = append(errs, errors.New("missing required setting: login-url")) | ||||||
|  | 	} | ||||||
|  | 	if providerConfig.RedeemURL == "" { | ||||||
|  | 		errs = append(errs, errors.New("missing required setting: redeem-url")) | ||||||
|  | 	} | ||||||
|  | 	if providerConfig.OIDCConfig.JwksURL == "" { | ||||||
|  | 		errs = append(errs, errors.New("missing required setting: oidc-jwks-url")) | ||||||
|  | 	} | ||||||
|  | 	if len(errs) > 0 { | ||||||
|  | 		return nil, k8serrors.NewAggregate(errs) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	keySet := oidc.NewRemoteKeySet(context.TODO(), providerConfig.OIDCConfig.JwksURL) | ||||||
|  | 	verificationOptions := &internaloidc.IDTokenVerificationOptions{ | ||||||
|  | 		AudienceClaims: providerConfig.OIDCConfig.AudienceClaims, | ||||||
|  | 		ClientID:       providerConfig.ClientID, | ||||||
|  | 		ExtraAudiences: providerConfig.OIDCConfig.ExtraAudiences, | ||||||
|  | 	} | ||||||
|  | 	verifier := internaloidc.NewVerifier(oidc.NewVerifier(providerConfig.OIDCConfig.IssuerURL, keySet, &oidc.Config{ | ||||||
|  | 		ClientID:          providerConfig.ClientID, | ||||||
|  | 		SkipIssuerCheck:   providerConfig.OIDCConfig.InsecureSkipIssuerVerification, | ||||||
|  | 		SkipClientIDCheck: true, // client id check is done within oauth2-proxy: IDTokenVerifier.Verify
 | ||||||
|  | 	}), verificationOptions) | ||||||
|  | 	return verifier, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,93 @@ | ||||||
|  | package providers | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
|  | 	. "github.com/onsi/gomega" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	clientID     = "bazquux" | ||||||
|  | 	clientSecret = "xyzzyplugh" | ||||||
|  | 	providerID   = "providerID" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestClientSecretFileOptionFails(t *testing.T) { | ||||||
|  | 	g := NewWithT(t) | ||||||
|  | 
 | ||||||
|  | 	providerConfig := options.Provider{ | ||||||
|  | 		ID:               providerID, | ||||||
|  | 		Type:             "google", | ||||||
|  | 		ClientID:         clientID, | ||||||
|  | 		ClientSecretFile: clientSecret, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p, err := newProviderDataFromConfig(providerConfig) | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 	g.Expect(p.ClientSecretFile).To(Equal(clientSecret)) | ||||||
|  | 	g.Expect(p.ClientSecret).To(BeEmpty()) | ||||||
|  | 
 | ||||||
|  | 	s, err := p.GetClientSecret() | ||||||
|  | 	g.Expect(err).To(HaveOccurred()) | ||||||
|  | 	g.Expect(s).To(BeEmpty()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestClientSecretFileOption(t *testing.T) { | ||||||
|  | 	g := NewWithT(t) | ||||||
|  | 
 | ||||||
|  | 	f, err := ioutil.TempFile("", "client_secret_temp_file_") | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 	clientSecretFileName := f.Name() | ||||||
|  | 
 | ||||||
|  | 	defer func() { | ||||||
|  | 		g.Expect(f.Close()).To(Succeed()) | ||||||
|  | 		g.Expect(os.Remove(clientSecretFileName)).To(Succeed()) | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	_, err = f.WriteString("testcase") | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 	providerConfig := options.Provider{ | ||||||
|  | 		ID:               providerID, | ||||||
|  | 		Type:             "google", | ||||||
|  | 		ClientID:         clientID, | ||||||
|  | 		ClientSecretFile: clientSecretFileName, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p, err := newProviderDataFromConfig(providerConfig) | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 	g.Expect(p.ClientSecretFile).To(Equal(clientSecretFileName)) | ||||||
|  | 	g.Expect(p.ClientSecret).To(BeEmpty()) | ||||||
|  | 
 | ||||||
|  | 	s, err := p.GetClientSecret() | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 	g.Expect(s).To(Equal("testcase")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestSkipOIDCDiscovery(t *testing.T) { | ||||||
|  | 	g := NewWithT(t) | ||||||
|  | 	providerConfig := options.Provider{ | ||||||
|  | 		ID:               providerID, | ||||||
|  | 		Type:             "oidc", | ||||||
|  | 		ClientID:         clientID, | ||||||
|  | 		ClientSecretFile: clientSecret, | ||||||
|  | 		OIDCConfig: options.OIDCOptions{ | ||||||
|  | 			IssuerURL:     "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/", | ||||||
|  | 			SkipDiscovery: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_, err := newProviderDataFromConfig(providerConfig) | ||||||
|  | 	g.Expect(err).To(MatchError("error setting OIDC configuration: [missing required setting: login-url, missing required setting: redeem-url, missing required setting: oidc-jwks-url]")) | ||||||
|  | 
 | ||||||
|  | 	providerConfig.LoginURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in" | ||||||
|  | 	providerConfig.RedeemURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_sign_in" | ||||||
|  | 	providerConfig.OIDCConfig.JwksURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys" | ||||||
|  | 
 | ||||||
|  | 	_, err = newProviderDataFromConfig(providerConfig) | ||||||
|  | 	g.Expect(err).ToNot(HaveOccurred()) | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue