Move provider initialisation into providers package

This commit is contained in:
Joel Speed 2022-02-15 11:18:32 +00:00
parent 95dd2745c7
commit d162b018a8
No known key found for this signature in database
GPG Key ID: 6E80578D6751DEFB
28 changed files with 786 additions and 211 deletions

View File

@ -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. |
| `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. |
| `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. |
| `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 |
@ -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 |
| `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
#### ([[]Provider](#provider) alias)

View File

@ -624,7 +624,7 @@ func (l *LegacyProvider) convert() (Providers, error) {
ClientID: l.ClientID,
ClientSecret: l.ClientSecret,
ClientSecretFile: l.ClientSecretFile,
Type: l.ProviderType,
Type: ProviderType(l.ProviderType),
CAFiles: l.ProviderCAFiles,
LoginURL: l.LoginURL,
RedeemURL: l.RedeemURL,

View File

@ -52,7 +52,7 @@ type Provider struct {
// Type is the OAuth provider
// must be set from the supported providers group,
// otherwise 'Google' is set as default
Type string `json:"provider,omitempty"`
Type ProviderType `json:"provider,omitempty"`
// Name is the providers display name
// if set, it will be shown to the users in the login page.
Name string `json:"name,omitempty"`
@ -84,6 +84,56 @@ type Provider struct {
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 {
// Group enables to restrict login to members of indicated group
Groups []string `json:"groups,omitempty"`

View File

@ -6,6 +6,7 @@ import (
"net/url"
"strings"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
)
@ -24,12 +25,11 @@ var _ Provider = (*ADFSProvider)(nil)
const (
adfsProviderName = "ADFS"
adfsDefaultScope = "openid email profile"
adfsSkipScope = false
adfsUPNClaim = "upn"
)
// NewADFSProvider initiates a new ADFSProvider
func NewADFSProvider(p *ProviderData) *ADFSProvider {
func NewADFSProvider(p *ProviderData, opts options.ADFSOptions) *ADFSProvider {
p.setProviderDefaults(providerDefaults{
name: adfsProviderName,
scope: adfsDefaultScope,
@ -53,17 +53,12 @@ func NewADFSProvider(p *ProviderData) *ADFSProvider {
return &ADFSProvider{
OIDCProvider: oidcProvider,
skipScope: adfsSkipScope,
skipScope: opts.SkipScope,
oidcEnrichFunc: oidcProvider.EnrichSession,
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
// 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 {

View File

@ -13,6 +13,7 @@ import (
"github.com/coreos/go-oidc/v3/oidc"
"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"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
. "github.com/onsi/ginkgo"
@ -61,8 +62,8 @@ func testADFSProvider(hostname string) *ADFSProvider {
ValidateURL: &url.URL{},
Scope: "",
Verifier: o,
EmailClaim: OIDCEmailClaim,
})
EmailClaim: options.OIDCEmailClaim,
}, options.ADFSOptions{})
if hostname != "" {
updateURL(p.Data().LoginURL, hostname)
@ -133,7 +134,7 @@ var _ = Describe("ADFS Provider Tests", func() {
Context("New Provider Init", func() {
It("uses defaults", func() {
providerData := NewADFSProvider(&ProviderData{}).Data()
providerData := NewADFSProvider(&ProviderData{}, options.ADFSOptions{}).Data()
Expect(providerData.ProviderName).To(Equal("ADFS"))
Expect(providerData.Scope).To(Equal("openid email profile"))
})
@ -165,8 +166,7 @@ var _ = Describe("ADFS Provider Tests", func() {
p := NewADFSProvider(&ProviderData{
ProtectedResource: resource,
Scope: "",
})
p.skipScope = true
}, options.ADFSOptions{SkipScope: true})
result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "")
Expect(result).NotTo(ContainSubstring("scope="))
@ -186,7 +186,7 @@ var _ = Describe("ADFS Provider Tests", func() {
p := NewADFSProvider(&ProviderData{
ProtectedResource: resource,
Scope: in.scope,
})
}, options.ADFSOptions{})
Expect(p.Data().Scope).To(Equal(in.expectedScope))
result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "")

View File

@ -10,6 +10,7 @@ import (
"time"
"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/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
@ -62,7 +63,7 @@ var (
)
// NewAzureProvider initiates a new AzureProvider
func NewAzureProvider(p *ProviderData) *AzureProvider {
func NewAzureProvider(p *ProviderData, opts options.AzureOptions) *AzureProvider {
p.setProviderDefaults(providerDefaults{
name: azureProviderName,
loginURL: azureDefaultLoginURL,
@ -80,25 +81,19 @@ func NewAzureProvider(p *ProviderData) *AzureProvider {
}
p.getAuthorizationHeaderFunc = makeAzureHeader
tenant := "common"
if opts.Tenant != "" {
tenant = opts.Tenant
overrideTenantURL(p.LoginURL, azureDefaultLoginURL, tenant, "authorize")
overrideTenantURL(p.RedeemURL, azureDefaultRedeemURL, tenant, "token")
}
return &AzureProvider{
ProviderData: p,
Tenant: "common",
Tenant: 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.RedeemURL, azureDefaultRedeemURL, tenant, "token")
}
func overrideTenantURL(current, defaultURL *url.URL, tenant, path string) {
if current == nil || current.String() == "" || current.String() == defaultURL.String() {
*current = url.URL{

View File

@ -15,6 +15,7 @@ import (
"github.com/coreos/go-oidc/v3/oidc"
"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"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
@ -39,7 +40,7 @@ type azureOAuthPayload struct {
IDToken string `json:"id_token,omitempty"`
}
func testAzureProvider(hostname string) *AzureProvider {
func testAzureProvider(hostname string, opts options.AzureOptions) *AzureProvider {
verificationOptions := &internaloidc.IDTokenVerificationOptions{
AudienceClaims: []string{"aud"},
ClientID: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532",
@ -64,7 +65,7 @@ func testAzureProvider(hostname string) *AzureProvider {
SkipExpiryCheck: true,
},
), verificationOptions),
})
}, opts)
if hostname != "" {
updateURL(p.Data().LoginURL, hostname)
@ -80,7 +81,7 @@ func TestNewAzureProvider(t *testing.T) {
g := NewWithT(t)
// 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.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"))
@ -111,7 +112,8 @@ func TestAzureProviderOverrides(t *testing.T) {
ProtectedResource: &url.URL{
Scheme: "https",
Host: "example.com"},
Scope: "profile"})
Scope: "profile"},
options.AzureOptions{})
assert.NotEqual(t, nil, p)
assert.Equal(t, "Azure", p.Data().ProviderName)
assert.Equal(t, "https://example.com/oauth/auth",
@ -128,8 +130,7 @@ func TestAzureProviderOverrides(t *testing.T) {
}
func TestAzureSetTenant(t *testing.T) {
p := testAzureProvider("")
p.Configure("example")
p := testAzureProvider("", options.AzureOptions{Tenant: "example"})
assert.Equal(t, "Azure", p.Data().ProviderName)
assert.Equal(t, "example", p.Tenant)
assert.Equal(t, "https://login.microsoftonline.com/example/oauth2/authorize",
@ -230,7 +231,7 @@ func TestAzureProviderEnrichSession(t *testing.T) {
bURL, _ := url.Parse(b.URL)
host = bURL.Host
}
p := testAzureProvider(host)
p := testAzureProvider(host, options.AzureOptions{})
session := CreateAuthorizedSession()
session.Email = testCase.Email
err := p.EnrichSession(context.Background(), session)
@ -323,7 +324,7 @@ func TestAzureProviderRedeem(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testAzureProvider(bURL.Host)
p := testAzureProvider(bURL.Host, options.AzureOptions{})
p.Data().RedeemURL.Path = "/common/oauth2/token"
s, err := p.Redeem(context.Background(), "https://localhost", "1234")
if testCase.InjectRedeemURLError {
@ -345,7 +346,7 @@ func TestAzureProviderRedeem(t *testing.T) {
}
func TestAzureProviderProtectedResourceConfigured(t *testing.T) {
p := testAzureProvider("")
p := testAzureProvider("", options.AzureOptions{})
p.ProtectedResource, _ = url.Parse("http://my.resource.test")
result := p.GetLoginURL("https://my.test.app/oauth", "", "")
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)
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testAzureProvider(bURL.Host)
p := testAzureProvider(bURL.Host, options.AzureOptions{})
expires := time.Now().Add(time.Duration(-1) * time.Hour)
session := &sessions.SessionState{AccessToken: "some_access_token", RefreshToken: refreshToken, IDToken: "some_id_token", ExpiresOn: &expires}

View File

@ -5,6 +5,7 @@ import (
"net/url"
"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/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
@ -53,7 +54,7 @@ var (
)
// NewBitbucketProvider initiates a new BitbucketProvider
func NewBitbucketProvider(p *ProviderData) *BitbucketProvider {
func NewBitbucketProvider(p *ProviderData, opts options.BitbucketOptions) *BitbucketProvider {
p.setProviderDefaults(providerDefaults{
name: bitbucketProviderName,
loginURL: bitbucketDefaultLoginURL,
@ -62,19 +63,28 @@ func NewBitbucketProvider(p *ProviderData) *BitbucketProvider {
validateURL: bitbucketDefaultValidateURL,
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
func (p *BitbucketProvider) SetTeam(team string) {
// setTeam defines the Bitbucket team the user must be part of
func (p *BitbucketProvider) setTeam(team string) {
p.Team = team
if !strings.Contains(p.Scope, "team") {
p.Scope += " team"
}
}
// SetRepository defines the repository the user must have access to
func (p *BitbucketProvider) SetRepository(repository string) {
// setRepository defines the repository the user must have access to
func (p *BitbucketProvider) setRepository(repository string) {
p.Repository = repository
if !strings.Contains(p.Scope, "repository") {
p.Scope += " repository"

View File

@ -8,6 +8,7 @@ import (
"net/url"
"testing"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
@ -21,15 +22,12 @@ func testBitbucketProvider(hostname, team string, repository string) *BitbucketP
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
Scope: ""})
if team != "" {
p.SetTeam(team)
}
if repository != "" {
p.SetRepository(repository)
}
Scope: ""},
options.BitbucketOptions{
Team: team,
Repository: repository,
},
)
if hostname != "" {
updateURL(p.Data().LoginURL, hostname)
@ -65,7 +63,7 @@ func TestNewBitbucketProvider(t *testing.T) {
g := NewWithT(t)
// 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.LoginURL.String()).To(Equal("https://bitbucket.org/site/oauth2/authorize"))
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",
Host: "example.com",
Path: "/api/v3/user"},
Scope: "profile"})
Scope: "profile"},
options.BitbucketOptions{})
assert.NotEqual(t, nil, p)
assert.Equal(t, "Bitbucket", p.Data().ProviderName)
assert.Equal(t, "https://example.com/oauth/auth",

View File

@ -11,6 +11,7 @@ import (
"strconv"
"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/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
@ -62,7 +63,7 @@ var (
)
// NewGitHubProvider initiates a new GitHubProvider
func NewGitHubProvider(p *ProviderData) *GitHubProvider {
func NewGitHubProvider(p *ProviderData, opts options.GitHubOptions) *GitHubProvider {
p.setProviderDefaults(providerDefaults{
name: githubProviderName,
loginURL: githubDefaultLoginURL,
@ -71,7 +72,13 @@ func NewGitHubProvider(p *ProviderData) *GitHubProvider {
validateURL: githubDefaultValidateURL,
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 {
@ -82,8 +89,8 @@ func makeGitHubHeader(accessToken string) http.Header {
return makeAuthorizationHeader(tokenTypeToken, accessToken, extraHeaders)
}
// SetOrgTeam adds GitHub org reading parameters to the OAuth2 scope
func (p *GitHubProvider) SetOrgTeam(org, team string) {
// setOrgTeam adds GitHub org reading parameters to the OAuth2 scope
func (p *GitHubProvider) setOrgTeam(org, team string) {
p.Org = org
p.Team = 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
func (p *GitHubProvider) SetRepo(repo, token string) {
// setRepo configures the target repository and optional token to use
func (p *GitHubProvider) setRepo(repo, token string) {
p.Repo = repo
p.Token = token
}
// SetUsers configures allowed usernames
func (p *GitHubProvider) SetUsers(users []string) {
// setUsers configures allowed usernames
func (p *GitHubProvider) setUsers(users []string) {
p.Users = users
}

View File

@ -7,12 +7,13 @@ import (
"net/url"
"testing"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
)
func testGitHubProvider(hostname string) *GitHubProvider {
func testGitHubProvider(hostname string, opts options.GitHubOptions) *GitHubProvider {
p := NewGitHubProvider(
&ProviderData{
ProviderName: "",
@ -20,7 +21,8 @@ func testGitHubProvider(hostname string) *GitHubProvider {
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
Scope: ""})
Scope: ""},
opts)
if hostname != "" {
updateURL(p.Data().LoginURL, hostname)
updateURL(p.Data().RedeemURL, hostname)
@ -71,7 +73,7 @@ func TestNewGitHubProvider(t *testing.T) {
g := NewWithT(t)
// 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.LoginURL.String()).To(Equal("https://github.com/login/oauth/authorize"))
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",
Host: "api.example.com",
Path: "/"},
Scope: "profile"})
Scope: "profile"},
options.GitHubOptions{})
assert.NotEqual(t, nil, p)
assert.Equal(t, "GitHub", p.Data().ProviderName)
assert.Equal(t, "https://example.com/login/oauth/authorize",
@ -114,7 +117,7 @@ func TestGitHubProvider_getEmail(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -129,7 +132,7 @@ func TestGitHubProvider_getEmailNotVerified(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -149,8 +152,11 @@ func TestGitHubProvider_getEmailWithOrg(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.Org = "testorg1"
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Org: "testorg1",
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -166,8 +172,12 @@ func TestGitHubProvider_getEmailWithWriteAccessToPublicRepo(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetRepo("oauth2-proxy/oauth2-proxy", "")
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Repo: "oauth2-proxy/oauth2-proxy",
Token: "token",
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -183,8 +193,12 @@ func TestGitHubProvider_getEmailWithReadOnlyAccessToPrivateRepo(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetRepo("oauth2-proxy/oauth2-proxy", "")
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Repo: "oauth2-proxy/oauth2-proxy",
Token: "token",
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -200,8 +214,12 @@ func TestGitHubProvider_getEmailWithWriteAccessToPrivateRepo(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetRepo("oauth2-proxy/oauth2-proxy", "")
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Repo: "oauth2-proxy/oauth2-proxy",
Token: "token",
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -216,8 +234,11 @@ func TestGitHubProvider_getEmailWithNoAccessToPrivateRepo(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetRepo("oauth2-proxy/oauth2-proxy", "")
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Repo: "oauth2-proxy/oauth2-proxy",
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -232,8 +253,12 @@ func TestGitHubProvider_getEmailWithToken(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetRepo("oauth2-proxy/oauth2-proxy", "token")
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Repo: "oauth2-proxy/oauth2-proxy",
Token: "token",
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -248,7 +273,7 @@ func TestGitHubProvider_getEmailFailedRequest(t *testing.T) {
defer b.Close()
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
// token. Alternatively, we could allow the parsing of the payload as
@ -266,7 +291,7 @@ func TestGitHubProvider_getEmailNotPresentInPayload(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -281,7 +306,7 @@ func TestGitHubProvider_getUser(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
session := CreateAuthorizedSession()
err := p.getUser(context.Background(), session)
@ -297,8 +322,12 @@ func TestGitHubProvider_getUserWithRepoAndToken(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetRepo("oauth2-proxy/oauth2-proxy", "token")
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Repo: "oauth2-proxy/oauth2-proxy",
Token: "token",
},
)
session := CreateAuthorizedSession()
err := p.getUser(context.Background(), session)
@ -311,8 +340,12 @@ func TestGitHubProvider_getUserWithRepoAndTokenWithoutPushAccess(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetRepo("oauth2-proxy/oauth2-proxy", "token")
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Repo: "oauth2-proxy/oauth2-proxy",
Token: "token",
},
)
session := CreateAuthorizedSession()
err := p.getUser(context.Background(), session)
@ -328,8 +361,11 @@ func TestGitHubProvider_getEmailWithUsername(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetUsers([]string{"mbland", "octocat"})
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Users: []string{"mbland", "octocat"},
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -345,8 +381,11 @@ func TestGitHubProvider_getEmailWithNotAllowedUsername(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetUsers([]string{"octocat"})
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Users: []string{"octocat"},
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -366,9 +405,12 @@ func TestGitHubProvider_getEmailWithUsernameAndNotBelongToOrg(t *testing.T) {
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetOrgTeam("not_belong_to", "")
p.SetUsers([]string{"mbland"})
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Org: "not_belog_to",
Users: []string{"mbland"},
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)
@ -385,9 +427,13 @@ func TestGitHubProvider_getEmailWithUsernameAndNoAccessToPrivateRepo(t *testing.
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.SetRepo("oauth2-proxy/oauth2-proxy", "")
p.SetUsers([]string{"mbland"})
p := testGitHubProvider(bURL.Host,
options.GitHubOptions{
Repo: "oauth2-proxy/oauth2-proxy",
Token: "token",
Users: []string{"mbland"},
},
)
session := CreateAuthorizedSession()
err := p.getEmail(context.Background(), session)

View File

@ -7,6 +7,7 @@ import (
"strconv"
"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/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
@ -30,7 +31,7 @@ type GitLabProvider struct {
var _ Provider = (*GitLabProvider)(nil)
// NewGitLabProvider initiates a new GitLabProvider
func NewGitLabProvider(p *ProviderData) *GitLabProvider {
func NewGitLabProvider(p *ProviderData, opts options.GitLabOptions) (*GitLabProvider, error) {
p.ProviderName = gitlabProviderName
if p.Scope == "" {
p.Scope = gitlabDefaultScope
@ -41,15 +42,22 @@ func NewGitLabProvider(p *ProviderData) *GitLabProvider {
SkipNonce: false,
}
return &GitLabProvider{
provider := &GitLabProvider{
OIDCProvider: oidcProvider,
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)
}
return provider, nil
}
// SetAllowedProjects adds Gitlab projects to the AllowedGroups list
// setAllowedProjects adds Gitlab projects to the AllowedGroups list
// 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 {
gp, err := newGitlabProject(project)
if err != nil {

View File

@ -7,21 +7,26 @@ import (
"net/http/httptest"
"net/url"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)
func testGitLabProvider(hostname string) *GitLabProvider {
p := NewGitLabProvider(
func testGitLabProvider(hostname, scope string, opts options.GitLabOptions) (*GitLabProvider, error) {
p, err := NewGitLabProvider(
&ProviderData{
ProviderName: "",
LoginURL: &url.URL{},
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
Scope: ""})
Scope: scope},
opts)
if err != nil {
return nil, err
}
if hostname != "" {
updateURL(p.Data().LoginURL, hostname)
updateURL(p.Data().RedeemURL, hostname)
@ -29,7 +34,7 @@ func testGitLabProvider(hostname string) *GitLabProvider {
updateURL(p.Data().ValidateURL, hostname)
}
return p
return p, err
}
func testGitLabBackend() *httptest.Server {
@ -157,7 +162,8 @@ var _ = Describe("Gitlab Provider Tests", func() {
bURL, err := url.Parse(b.URL)
Expect(err).To(BeNil())
p = testGitLabProvider(bURL.Host)
p, err = testGitLabProvider(bURL.Host, "", options.GitLabOptions{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
@ -219,21 +225,23 @@ var _ = Describe("Gitlab Provider Tests", func() {
DescribeTable("should return expected results",
func(in entitiesTableInput) {
p.AllowUnverifiedEmail = true
if in.scope != "" {
p.Scope = in.scope
}
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
bURL, err := url.Parse(b.URL)
Expect(err).To(BeNil())
p.SetAllowedGroups(in.allowedGroups)
err := p.SetAllowedProjects(in.allowedProjects)
p, err := testGitLabProvider(bURL.Host, in.scope, options.GitLabOptions{
Group: in.allowedGroups,
Projects: in.allowedProjects,
})
if in.expectedError == nil {
Expect(err).To(BeNil())
} else {
Expect(err).To(MatchError(in.expectedError))
return
}
p.AllowUnverifiedEmail = true
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
Expect(p.Scope).To(Equal(in.expectedScope))
err = p.EnrichSession(context.Background(), session)
@ -302,7 +310,7 @@ var _ = Describe("Gitlab Provider Tests", func() {
}),
Entry("invalid project format", entitiesTableInput{
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",
}),
)

View File

@ -10,9 +10,11 @@ import (
"io"
"io/ioutil"
"net/url"
"os"
"strings"
"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/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
@ -79,7 +81,7 @@ var (
)
// NewGoogleProvider initiates a new GoogleProvider
func NewGoogleProvider(p *ProviderData) *GoogleProvider {
func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProvider, error) {
p.setProviderDefaults(providerDefaults{
name: googleProviderName,
loginURL: googleDefaultLoginURL,
@ -88,7 +90,7 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider {
validateURL: googleDefaultValidateURL,
scope: googleDefaultScope,
})
return &GoogleProvider{
provider := &GoogleProvider{
ProviderData: p,
// Set a default groupValidator to just always return valid (true), it will
// be overwritten if we configured a Google group restriction.
@ -96,6 +98,21 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider {
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) {
@ -195,7 +212,7 @@ func (p *GoogleProvider) EnrichSession(_ context.Context, s *sessions.SessionSta
// account credentials.
//
// 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)
p.groupValidator = func(s *sessions.SessionState) bool {
// Reset our saved Groups in case membership changed

View File

@ -10,6 +10,7 @@ import (
"net/url"
"testing"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
@ -25,22 +26,29 @@ func newRedeemServer(body []byte) (*url.URL, *httptest.Server) {
return u, s
}
func newGoogleProvider() *GoogleProvider {
return NewGoogleProvider(
func newGoogleProvider(t *testing.T) *GoogleProvider {
g := NewWithT(t)
p, err := NewGoogleProvider(
&ProviderData{
ProviderName: "",
LoginURL: &url.URL{},
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
Scope: ""})
Scope: ""},
options.GoogleOptions{})
g.Expect(err).ToNot(HaveOccurred())
return p
}
func TestNewGoogleProvider(t *testing.T) {
g := NewWithT(t)
// 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.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"))
@ -50,7 +58,7 @@ func TestNewGoogleProvider(t *testing.T) {
}
func TestGoogleProviderOverrides(t *testing.T) {
p := NewGoogleProvider(
p, err := NewGoogleProvider(
&ProviderData{
LoginURL: &url.URL{
Scheme: "https",
@ -68,7 +76,9 @@ func TestGoogleProviderOverrides(t *testing.T) {
Scheme: "https",
Host: "example.com",
Path: "/oauth/tokeninfo"},
Scope: "profile"})
Scope: "profile"},
options.GoogleOptions{})
assert.NoError(t, err)
assert.NotEqual(t, nil, p)
assert.Equal(t, "Google", p.Data().ProviderName)
assert.Equal(t, "https://example.com/oauth/auth",
@ -90,7 +100,7 @@ type redeemResponse struct {
}
func TestGoogleProviderGetEmailAddress(t *testing.T) {
p := newGoogleProvider()
p := newGoogleProvider(t)
body, err := json.Marshal(redeemResponse{
AccessToken: "a1234",
ExpiresIn: 10,
@ -147,7 +157,7 @@ func TestGoogleProviderGroupValidator(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
g := NewWithT(t)
p := newGoogleProvider()
p := newGoogleProvider(t)
if tc.validatorFunc != nil {
p.groupValidator = tc.validatorFunc
}
@ -158,7 +168,7 @@ func TestGoogleProviderGroupValidator(t *testing.T) {
//
func TestGoogleProviderGetEmailAddressInvalidEncoding(t *testing.T) {
p := newGoogleProvider()
p := newGoogleProvider(t)
body, err := json.Marshal(redeemResponse{
AccessToken: "a1234",
IDToken: "ignored prefix." + `{"email": "michael.bland@gsa.gov"}`,
@ -176,7 +186,7 @@ func TestGoogleProviderGetEmailAddressInvalidEncoding(t *testing.T) {
}
func TestGoogleProviderRedeemFailsNoCLientSecret(t *testing.T) {
p := newGoogleProvider()
p := newGoogleProvider(t)
p.ProviderData.ClientSecretFile = "srvnoerre"
session, err := p.Redeem(context.Background(), "http://redirect/", "code1234")
@ -188,7 +198,7 @@ func TestGoogleProviderRedeemFailsNoCLientSecret(t *testing.T) {
}
func TestGoogleProviderGetEmailAddressInvalidJson(t *testing.T) {
p := newGoogleProvider()
p := newGoogleProvider(t)
body, err := json.Marshal(redeemResponse{
AccessToken: "a1234",
@ -208,7 +218,7 @@ func TestGoogleProviderGetEmailAddressInvalidJson(t *testing.T) {
}
func TestGoogleProviderGetEmailAddressEmailMissing(t *testing.T) {
p := newGoogleProvider()
p := newGoogleProvider(t)
body, err := json.Marshal(redeemResponse{
AccessToken: "a1234",
IDToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"not_email": "missing"}`)),

View File

@ -5,6 +5,7 @@ import (
"fmt"
"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/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
@ -48,7 +49,7 @@ var (
)
// NewKeycloakProvider creates a KeyCloakProvider using the passed ProviderData
func NewKeycloakProvider(p *ProviderData) *KeycloakProvider {
func NewKeycloakProvider(p *ProviderData, opts options.KeycloakOptions) *KeycloakProvider {
p.setProviderDefaults(providerDefaults{
name: keycloakProviderName,
loginURL: keycloakDefaultLoginURL,
@ -57,7 +58,10 @@ func NewKeycloakProvider(p *ProviderData) *KeycloakProvider {
validateURL: keycloakDefaultValidateURL,
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

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
)
@ -15,21 +16,25 @@ type KeycloakOIDCProvider struct {
}
// NewKeycloakOIDCProvider makes a KeycloakOIDCProvider using the ProviderData
func NewKeycloakOIDCProvider(p *ProviderData) *KeycloakOIDCProvider {
func NewKeycloakOIDCProvider(p *ProviderData, opts options.KeycloakOptions) *KeycloakOIDCProvider {
p.ProviderName = keycloakOIDCProviderName
return &KeycloakOIDCProvider{
provider := &KeycloakOIDCProvider{
OIDCProvider: &OIDCProvider{
ProviderData: p,
},
}
provider.addAllowedRoles(opts.Roles)
return provider
}
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
// with `role:` prefixed roles.
func (p *KeycloakOIDCProvider) AddAllowedRoles(roles []string) {
func (p *KeycloakOIDCProvider) addAllowedRoles(roles []string) {
if p.AllowedGroups == nil {
p.AllowedGroups = make(map[string]struct{})
}

View File

@ -9,6 +9,7 @@ import (
"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/onsi/ginkgo"
. "github.com/onsi/gomega"
@ -39,11 +40,11 @@ func getAccessToken() string {
func newTestKeycloakOIDCSetup() (*httptest.Server, *KeycloakOIDCProvider) {
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
}
func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
func newKeycloakOIDCProvider(serverURL *url.URL, opts options.KeycloakOptions) *KeycloakOIDCProvider {
verificationOptions := &internaloidc.IDTokenVerificationOptions{
AudienceClaims: []string{defaultAudienceClaim},
ClientID: mockClientID,
@ -66,7 +67,8 @@ func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
Scheme: "https",
Host: "keycloak-oidc.com",
Path: "/api/v3/user"},
Scope: "openid email profile"})
Scope: "openid email profile"},
opts)
if serverURL != nil {
p.RedeemURL.Scheme = serverURL.Scheme
@ -88,7 +90,7 @@ func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
var _ = Describe("Keycloak OIDC Provider Tests", func() {
Context("New Provider Init", func() {
It("creates new keycloak oidc provider with expected defaults", func() {
p := newKeycloakOIDCProvider(nil)
p := newKeycloakOIDCProvider(nil, options.KeycloakOptions{})
providerData := p.Data()
Expect(providerData.ProviderName).To(Equal(keycloakOIDCProviderName))
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() {
It("should prefix allowed roles and add them to groups", func() {
p := newKeycloakOIDCProvider(nil)
p.AddAllowedRoles([]string{"admin", "editor"})
p := newKeycloakOIDCProvider(nil, options.KeycloakOptions{
Roles: []string{"admin", "editor"},
})
Expect(p.AllowedGroups).To(HaveKey("role:admin"))
Expect(p.AllowedGroups).To(HaveKey("role:editor"))
})

View File

@ -8,6 +8,7 @@ import (
"net/http/httptest"
"net/url"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
@ -27,7 +28,8 @@ func testKeycloakProvider(backend *httptest.Server) (*KeycloakProvider, error) {
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
Scope: ""})
Scope: ""},
options.KeycloakOptions{})
if backend != nil {
bURL, err := url.Parse(backend.URL)
@ -48,7 +50,7 @@ func testKeycloakProvider(backend *httptest.Server) (*KeycloakProvider, error) {
var _ = Describe("Keycloak Provider Tests", func() {
Context("New Provider Init", func() {
It("uses defaults", func() {
providerData := NewKeycloakProvider(&ProviderData{}).Data()
providerData := NewKeycloakProvider(&ProviderData{}, options.KeycloakOptions{}).Data()
Expect(providerData.ProviderName).To(Equal("Keycloak"))
Expect(providerData.LoginURL.String()).To(Equal("https://keycloak.org/oauth/authorize"))
Expect(providerData.RedeemURL.String()).To(Equal("https://keycloak.org/oauth/token"))
@ -76,7 +78,8 @@ var _ = Describe("Keycloak Provider Tests", func() {
Scheme: "https",
Host: "example.com",
Path: "/api/v3/user"},
Scope: "profile"})
Scope: "profile"},
options.KeycloakOptions{})
providerData := p.Data()
Expect(providerData.ProviderName).To(Equal("Keycloak"))

View File

@ -5,12 +5,15 @@ import (
"context"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net/url"
"time"
"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/requests"
"gopkg.in/square/go-jose.v2"
@ -78,7 +81,7 @@ var (
)
// NewLoginGovProvider initiates a new LoginGovProvider
func NewLoginGovProvider(p *ProviderData) *LoginGovProvider {
func NewLoginGovProvider(p *ProviderData, opts options.LoginGovOptions) (*LoginGovProvider, error) {
p.setProviderDefaults(providerDefaults{
name: loginGovProviderName,
loginURL: loginGovDefaultLoginURL,
@ -87,10 +90,50 @@ func NewLoginGovProvider(p *ProviderData) *LoginGovProvider {
validateURL: loginGovDefaultProfileURL,
scope: loginGovDefaultScope,
})
return &LoginGovProvider{
provider := &LoginGovProvider{
ProviderData: p,
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 {

View File

@ -1,11 +1,14 @@
package providers
import (
"bytes"
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"net/http"
"net/http/httptest"
"net/url"
@ -13,6 +16,7 @@ import (
"time"
"github.com/golang-jwt/jwt"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
"gopkg.in/square/go-jose.v2"
@ -32,12 +36,35 @@ func newLoginGovServer(body []byte) (*url.URL, *httptest.Server) {
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)
if err != nil {
return
return nil, nil, err
}
serverKey = &MyKeyData{
serverKey := &MyKeyData{
PubKey: key.Public(),
PrivKey: key,
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 {
return
return nil, nil, err
}
l = NewLoginGovProvider(
l, err := NewLoginGovProvider(
&ProviderData{
ProviderName: "",
LoginURL: &url.URL{},
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
Scope: ""})
l.JWTKey = privateKey
Scope: ""},
options.LoginGovOptions{
JWTKey: string(privKey),
},
)
l.Nonce = "fakenonce"
return
return l, serverKey, err
}
func TestNewLoginGovProvider(t *testing.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
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.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"))
@ -80,7 +118,10 @@ func TestNewLoginGovProvider(t *testing.T) {
}
func TestLoginGovProviderOverrides(t *testing.T) {
p := NewLoginGovProvider(
privKey, err := newPrivateKeyBytes()
assert.NoError(t, err)
p, err := NewLoginGovProvider(
&ProviderData{
LoginURL: &url.URL{
Scheme: "https",
@ -94,7 +135,11 @@ func TestLoginGovProviderOverrides(t *testing.T) {
Scheme: "https",
Host: "example.com",
Path: "/oauth/profile"},
Scope: "profile"})
Scope: "profile"},
options.LoginGovOptions{
JWTKey: string(privKey),
})
assert.NoError(t, err)
assert.NotEqual(t, nil, p)
assert.Equal(t, "login.gov", p.Data().ProviderName)
assert.Equal(t, "https://example.com/oauth/auth",

View File

@ -1,5 +1,7 @@
package providers
import "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
// NextcloudProvider represents an Nextcloud based Identity Provider
type NextcloudProvider struct {
*ProviderData
@ -13,7 +15,7 @@ const nextCloudProviderName = "Nextcloud"
func NewNextcloudProvider(p *ProviderData) *NextcloudProvider {
p.ProviderName = nextCloudProviderName
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
// for this provider
p.EmailClaim = "ocs.data.email"

View File

@ -7,6 +7,7 @@ import (
"net/url"
"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/logger"
"golang.org/x/oauth2"
@ -20,13 +21,13 @@ type OIDCProvider struct {
}
// NewOIDCProvider initiates a new OIDCProvider
func NewOIDCProvider(p *ProviderData) *OIDCProvider {
func NewOIDCProvider(p *ProviderData, opts options.OIDCOptions) *OIDCProvider {
p.ProviderName = "OpenID Connect"
p.getAuthorizationHeaderFunc = makeOIDCHeader
return &OIDCProvider{
ProviderData: p,
SkipNonce: true,
SkipNonce: opts.InsecureSkipNonce,
}
}

View File

@ -11,6 +11,7 @@ import (
"testing"
"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/encryption"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
@ -25,7 +26,7 @@ type redeemTokenResponse struct {
IDToken string `json:"id_token,omitempty"`
}
func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
func newOIDCProvider(serverURL *url.URL, skipNonce bool) *OIDCProvider {
verificationOptions := &internaloidc.IDTokenVerificationOptions{
AudienceClaims: []string{"aud"},
ClientID: "https://test.myapp.com",
@ -61,7 +62,9 @@ func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
), verificationOptions),
}
p := NewOIDCProvider(providerData)
p := NewOIDCProvider(providerData, options.OIDCOptions{
InsecureSkipNonce: skipNonce,
})
return p
}
@ -77,7 +80,7 @@ func newOIDCServer(body []byte) (*url.URL, *httptest.Server) {
func newTestOIDCSetup(body []byte) (*httptest.Server, *OIDCProvider) {
redeemURL, server := newOIDCServer(body)
provider := newOIDCProvider(redeemURL)
provider := newOIDCProvider(redeemURL, false)
return server, provider
}
@ -86,7 +89,7 @@ func TestOIDCProviderGetLoginURL(t *testing.T) {
Scheme: "https",
Host: "oauth2proxy.oidctest",
}
provider := newOIDCProvider(serverURL)
provider := newOIDCProvider(serverURL, true)
n, err := encryption.Nonce()
assert.NoError(t, err)

View File

@ -10,6 +10,7 @@ import (
"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/logger"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
@ -18,14 +19,10 @@ import (
)
const (
OIDCEmailClaim = "email"
OIDCGroupsClaim = "groups"
// This is not exported as it's not currently user configurable
oidcUserClaim = "sub"
)
var OIDCAudienceClaims = []string{"aud"}
// ProviderData contains information required to configure all implementations
// of OAuth2 providers
type ProviderData struct {
@ -76,9 +73,9 @@ func (p *ProviderData) GetClientSecret() (clientSecret string, err error) {
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
func (p *ProviderData) SetAllowedGroups(groups []string) {
func (p *ProviderData) setAllowedGroups(groups []string) {
p.AllowedGroups = make(map[string]struct{}, len(groups))
for _, group := range groups {
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
// considered unverified.
verifyEmail := (p.EmailClaim == OIDCEmailClaim) && !p.AllowUnverifiedEmail
verifyEmail := (p.EmailClaim == options.OIDCEmailClaim) && !p.AllowUnverifiedEmail
var verified bool
exists, err := extractor.GetClaimInto("email_verified", &verified)

View File

@ -107,7 +107,7 @@ func TestProviderDataAuthorize(t *testing.T) {
Groups: tc.groups,
}
p := &ProviderData{}
p.SetAllowedGroups(tc.allowedGroups)
p.setAllowedGroups(tc.allowedGroups)
authorized, err := p.Authorize(context.Background(), session)
g.Expect(err).ToNot(HaveOccurred())

View File

@ -2,8 +2,18 @@ package providers
import (
"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/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
@ -20,38 +30,247 @@ type Provider interface {
CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error)
}
// New provides a new Provider based on the configured provider string
func New(provider string, p *ProviderData) Provider {
switch provider {
case "linkedin":
return NewLinkedInProvider(p)
case "facebook":
return NewFacebookProvider(p)
case "github":
return NewGitHubProvider(p)
case "keycloak":
return NewKeycloakProvider(p)
case "keycloak-oidc":
return NewKeycloakOIDCProvider(p)
case "azure":
return NewAzureProvider(p)
case "adfs":
return NewADFSProvider(p)
case "gitlab":
return NewGitLabProvider(p)
case "oidc":
return NewOIDCProvider(p)
case "login.gov":
return NewLoginGovProvider(p)
case "bitbucket":
return NewBitbucketProvider(p)
case "nextcloud":
return NewNextcloudProvider(p)
case "digitalocean":
return NewDigitalOceanProvider(p)
case "google":
return NewGoogleProvider(p)
func NewProvider(providerConfig options.Provider) (Provider, error) {
providerData, err := newProviderDataFromConfig(providerConfig)
if err != nil {
return nil, fmt.Errorf("could not create provider data: %v", err)
}
switch providerConfig.Type {
case options.ADFSProvider:
return NewADFSProvider(providerData, providerConfig.ADFSConfig), nil
case options.AzureProvider:
return NewAzureProvider(providerData, providerConfig.AzureConfig), nil
case options.BitbucketProvider:
return NewBitbucketProvider(providerData, providerConfig.BitbucketConfig), nil
case options.DigitalOceanProvider:
return NewDigitalOceanProvider(providerData), nil
case options.FacebookProvider:
return NewFacebookProvider(providerData), nil
case options.GitHubProvider:
return NewGitHubProvider(providerData, providerConfig.GitHubConfig), nil
case options.GitLabProvider:
return NewGitLabProvider(providerData, providerConfig.GitLabConfig)
case options.GoogleProvider:
return NewGoogleProvider(providerData, providerConfig.GoogleConfig)
case options.KeycloakProvider:
return NewKeycloakProvider(providerData, providerConfig.KeycloakConfig), nil
case options.KeycloakOIDCProvider:
return NewKeycloakOIDCProvider(providerData, providerConfig.KeycloakConfig), nil
case options.LinkedInProvider:
return NewLinkedInProvider(providerData), nil
case options.LoginGovProvider:
return NewLoginGovProvider(providerData, providerConfig.LoginGovConfig)
case options.NextCloudProvider:
return NewNextcloudProvider(providerData), nil
case options.OIDCProvider:
return NewOIDCProvider(providerData, providerConfig.OIDCConfig), nil
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
}

View File

@ -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())
}