405 lines
11 KiB
Go
405 lines
11 KiB
Go
package providers
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/oauth2-proxy/mockoidc"
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
const (
|
|
clientID = "bazquux"
|
|
clientSecret = "xyzzyplugh"
|
|
providerID = "providerID"
|
|
|
|
msIssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/"
|
|
msKeysURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys"
|
|
msAuthURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in"
|
|
msTokenURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_sign_in"
|
|
)
|
|
|
|
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 := os.CreateTemp("", "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: msIssuerURL,
|
|
SkipDiscovery: ptr.To(true),
|
|
},
|
|
}
|
|
|
|
_, err := newProviderDataFromConfig(providerConfig)
|
|
g.Expect(err).To(MatchError("error building OIDC ProviderVerifier: invalid provider verifier options: missing required setting: jwks-url or public-key-files"))
|
|
|
|
providerConfig.LoginURL = msAuthURL
|
|
providerConfig.RedeemURL = msTokenURL
|
|
providerConfig.OIDCConfig.JwksURL = msKeysURL
|
|
|
|
_, err = newProviderDataFromConfig(providerConfig)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
|
|
func TestURLsCorrectlyParsed(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
providerConfig := options.Provider{
|
|
ID: providerID,
|
|
Type: "oidc",
|
|
ClientID: clientID,
|
|
ClientSecretFile: clientSecret,
|
|
LoginURL: msAuthURL,
|
|
RedeemURL: msTokenURL,
|
|
OIDCConfig: options.OIDCOptions{
|
|
IssuerURL: msIssuerURL,
|
|
SkipDiscovery: ptr.To(true),
|
|
JwksURL: msKeysURL,
|
|
},
|
|
}
|
|
|
|
pd, err := newProviderDataFromConfig(providerConfig)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
g.Expect(pd.LoginURL.String()).To(Equal(msAuthURL))
|
|
g.Expect(pd.RedeemURL.String()).To(Equal(msTokenURL))
|
|
}
|
|
|
|
func TestEnabledSigningAlgsAreAppliedToProviderVerifier(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
m, err := mockoidc.NewServer(nil)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
m.AddMiddleware(newSigningAlgsIssuerMiddleware(m, []string{"RS256", "HS256"}))
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(m.Start(listener, nil)).To(Succeed())
|
|
defer func() {
|
|
g.Expect(m.Shutdown()).To(Succeed())
|
|
}()
|
|
|
|
providerConfig := options.Provider{
|
|
ID: providerID,
|
|
Type: "oidc",
|
|
ClientID: m.Config().ClientID,
|
|
ClientSecretFile: clientSecret,
|
|
OIDCConfig: options.OIDCOptions{
|
|
IssuerURL: m.Issuer(),
|
|
AudienceClaims: []string{"aud"},
|
|
EnabledSigningAlgs: []string{"HS256"},
|
|
},
|
|
}
|
|
|
|
pd, err := newProviderDataFromConfig(providerConfig)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
rawIDToken, err := m.Keypair.SignJWT(jwt.RegisteredClaims{
|
|
Audience: jwt.ClaimStrings{m.Config().ClientID},
|
|
Issuer: m.Issuer(),
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
Subject: "user",
|
|
})
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
_, err = pd.Verifier.Verify(context.Background(), rawIDToken)
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(err.Error()).To(ContainSubstring("unexpected signature algorithm"))
|
|
}
|
|
|
|
func TestEnabledSigningAlgsRejectUnsupportedTokens(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
m, err := mockoidc.Run()
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
defer func() {
|
|
g.Expect(m.Shutdown()).To(Succeed())
|
|
}()
|
|
|
|
providerConfig := options.Provider{
|
|
ID: providerID,
|
|
Type: "oidc",
|
|
ClientID: m.Config().ClientID,
|
|
ClientSecretFile: clientSecret,
|
|
LoginURL: m.AuthorizationEndpoint(),
|
|
RedeemURL: m.TokenEndpoint(),
|
|
OIDCConfig: options.OIDCOptions{
|
|
IssuerURL: m.Issuer(),
|
|
SkipDiscovery: ptr.To(true),
|
|
JwksURL: m.JWKSEndpoint(),
|
|
AudienceClaims: []string{"aud"},
|
|
EnabledSigningAlgs: []string{"HS256"},
|
|
},
|
|
}
|
|
|
|
pd, err := newProviderDataFromConfig(providerConfig)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
rawIDToken, err := m.Keypair.SignJWT(jwt.RegisteredClaims{
|
|
Audience: jwt.ClaimStrings{m.Config().ClientID},
|
|
Issuer: m.Issuer(),
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
Subject: "user",
|
|
})
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
_, err = pd.Verifier.Verify(context.Background(), rawIDToken)
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(err.Error()).To(ContainSubstring("unexpected signature algorithm"))
|
|
}
|
|
|
|
func TestScope(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
configuredType options.ProviderType
|
|
configuredScope string
|
|
expectedScope string
|
|
allowedGroups []string
|
|
}{
|
|
{
|
|
name: "oidc: with no scope provided",
|
|
configuredType: "oidc",
|
|
configuredScope: "",
|
|
expectedScope: "openid email profile",
|
|
},
|
|
{
|
|
name: "oidc: with no scope provided and allowed groups",
|
|
configuredType: "oidc",
|
|
configuredScope: "",
|
|
expectedScope: "openid email profile groups",
|
|
allowedGroups: []string{"foo"},
|
|
},
|
|
{
|
|
name: "oidc: with custom scope including groups without allowed groups",
|
|
configuredType: "oidc",
|
|
configuredScope: "myscope groups",
|
|
expectedScope: "myscope groups",
|
|
},
|
|
{
|
|
name: "oidc: with custom scope without groups but allowed groups",
|
|
configuredType: "oidc",
|
|
configuredScope: "myscope",
|
|
expectedScope: "myscope",
|
|
allowedGroups: []string{"foo"},
|
|
},
|
|
{
|
|
name: "oidc: with custom scope with groups and allowed groups",
|
|
configuredType: "oidc",
|
|
configuredScope: "myscope groups",
|
|
expectedScope: "myscope groups",
|
|
allowedGroups: []string{"foo"},
|
|
},
|
|
{
|
|
name: "oidc: with a configured scope provided",
|
|
configuredType: "oidc",
|
|
configuredScope: "openid",
|
|
expectedScope: "openid",
|
|
},
|
|
{
|
|
name: "github: with no scope provided",
|
|
configuredType: "github",
|
|
configuredScope: "",
|
|
expectedScope: "user:email read:org",
|
|
},
|
|
{
|
|
name: "github: with a configured scope provided",
|
|
configuredType: "github",
|
|
configuredScope: "read:user read:org",
|
|
expectedScope: "read:user read:org",
|
|
},
|
|
{
|
|
name: "keycloak: with no scope provided and groups",
|
|
configuredType: "keycloak-oidc",
|
|
configuredScope: "",
|
|
expectedScope: "openid email profile groups",
|
|
allowedGroups: []string{"foo"},
|
|
},
|
|
{
|
|
name: "keycloak: with custom scope and groups",
|
|
configuredType: "keycloak-oidc",
|
|
configuredScope: "myscope",
|
|
expectedScope: "myscope",
|
|
allowedGroups: []string{"foo"},
|
|
},
|
|
{
|
|
name: "keycloak: with custom scope and groups scope",
|
|
configuredType: "keycloak-oidc",
|
|
configuredScope: "myscope groups",
|
|
expectedScope: "myscope groups",
|
|
allowedGroups: []string{"foo"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
providerConfig := options.Provider{
|
|
ID: providerID,
|
|
Type: tc.configuredType,
|
|
ClientID: clientID,
|
|
ClientSecretFile: clientSecret,
|
|
LoginURL: msAuthURL,
|
|
RedeemURL: msTokenURL,
|
|
Scope: tc.configuredScope,
|
|
AllowedGroups: tc.allowedGroups,
|
|
OIDCConfig: options.OIDCOptions{
|
|
IssuerURL: msIssuerURL,
|
|
SkipDiscovery: ptr.To(true),
|
|
JwksURL: msKeysURL,
|
|
},
|
|
}
|
|
|
|
pd, err := NewProvider(providerConfig)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
g.Expect(pd.Data().Scope).To(Equal(tc.expectedScope))
|
|
}
|
|
}
|
|
|
|
func TestForcedMethodS256(t *testing.T) {
|
|
g := NewWithT(t)
|
|
options := options.NewOptions()
|
|
options.Providers[0].CodeChallengeMethod = CodeChallengeMethodS256
|
|
method := parseCodeChallengeMethod(options.Providers[0])
|
|
|
|
g.Expect(method).To(Equal(CodeChallengeMethodS256))
|
|
}
|
|
|
|
func TestForcedMethodPlain(t *testing.T) {
|
|
g := NewWithT(t)
|
|
options := options.NewOptions()
|
|
options.Providers[0].CodeChallengeMethod = CodeChallengeMethodPlain
|
|
method := parseCodeChallengeMethod(options.Providers[0])
|
|
|
|
g.Expect(method).To(Equal(CodeChallengeMethodPlain))
|
|
}
|
|
|
|
func TestPrefersS256(t *testing.T) {
|
|
g := NewWithT(t)
|
|
options := options.NewOptions()
|
|
method := parseCodeChallengeMethod(options.Providers[0])
|
|
|
|
g.Expect(method).To(Equal(""))
|
|
}
|
|
|
|
func TestCanOverwriteS256(t *testing.T) {
|
|
g := NewWithT(t)
|
|
options := options.NewOptions()
|
|
options.Providers[0].CodeChallengeMethod = "plain"
|
|
method := parseCodeChallengeMethod(options.Providers[0])
|
|
|
|
g.Expect(method).To(Equal(CodeChallengeMethodPlain))
|
|
}
|
|
|
|
func TestEmailClaimCorrectlySet(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
userIDClaim string
|
|
emailClaim string
|
|
expectedEmailClaim string
|
|
}{
|
|
{
|
|
name: "do not override EmailClaim if UserIDClaim is empty",
|
|
userIDClaim: "",
|
|
emailClaim: "email",
|
|
expectedEmailClaim: "email",
|
|
},
|
|
{
|
|
name: "set EmailClaim to UserIDClaim",
|
|
userIDClaim: "user_id_claim",
|
|
emailClaim: "email",
|
|
expectedEmailClaim: "user_id_claim",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
providerConfig := options.Provider{
|
|
ID: providerID,
|
|
Type: "oidc",
|
|
ClientID: clientID,
|
|
ClientSecretFile: clientSecret,
|
|
LoginURL: msAuthURL,
|
|
RedeemURL: msTokenURL,
|
|
OIDCConfig: options.OIDCOptions{
|
|
IssuerURL: msIssuerURL,
|
|
SkipDiscovery: ptr.To(true),
|
|
JwksURL: msKeysURL,
|
|
UserIDClaim: tc.userIDClaim,
|
|
EmailClaim: tc.emailClaim,
|
|
},
|
|
}
|
|
|
|
pd, err := newProviderDataFromConfig(providerConfig)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
g.Expect(pd.EmailClaim).To(Equal(tc.expectedEmailClaim))
|
|
})
|
|
}
|
|
}
|