@@ -0,0 +1,173 @@
1+package oidc
2+
3+import (
4+ "context"
5+ "time"
6+
7+ "github.com/dgrijalva/jwt-go"
8+ "github.com/oauth2-proxy/mockoidc"
9+ . "github.com/onsi/ginkgo"
10+ . "github.com/onsi/ginkgo/extensions/table"
11+ . "github.com/onsi/gomega"
12+)
13+
14+var _ = Describe("ProviderVerifier", func() {
15+ var m *mockoidc.MockOIDC
16+
17+ BeforeEach(func() {
18+ var err error
19+ m, err = mockoidc.Run()
20+ Expect(err).ToNot(HaveOccurred())
21+ })
22+
23+ AfterEach(func() {
24+ Expect(m.Shutdown()).To(Succeed())
25+ })
26+
27+ type newProviderVerifierTableInput struct {
28+ modifyOpts func(*ProviderVerifierOptions)
29+ expectedError string
30+ }
31+
32+ DescribeTable("when constructing the provider verifier", func(in *newProviderVerifierTableInput) {
33+ opts := ProviderVerifierOptions{
34+ AudienceClaims: []string{"aud"},
35+ ClientID: m.Config().ClientID,
36+ ExtraAudiences: []string{},
37+ IssuerURL: m.Issuer(),
38+ }
39+ if in.modifyOpts != nil {
40+ in.modifyOpts(&opts)
41+ }
42+
43+ pv, err := NewProviderVerifier(context.Background(), opts)
44+ if in.expectedError != "" {
45+ Expect(err).To(MatchError(HavePrefix(in.expectedError)))
46+ return
47+ }
48+ Expect(err).ToNot(HaveOccurred())
49+
50+ Expect(pv.DiscoveryEnabled()).ToNot(Equal(opts.SkipDiscovery), "DiscoveryEnabled should be the reverse of skip discovery")
51+ Expect(pv.Provider()).ToNot(BeNil())
52+
53+ if pv.DiscoveryEnabled() {
54+ endpoints := pv.Provider().Endpoints()
55+ Expect(endpoints.AuthURL).To(Equal(m.AuthorizationEndpoint()))
56+ Expect(endpoints.TokenURL).To(Equal(m.TokenEndpoint()))
57+ Expect(endpoints.JWKsURL).To(Equal(m.JWKSEndpoint()))
58+ Expect(endpoints.UserInfoURL).To(Equal(m.UserinfoEndpoint()))
59+ }
60+ },
61+ Entry("should be succesfful when discovering the OIDC provider", &newProviderVerifierTableInput{
62+ modifyOpts: func(_ *ProviderVerifierOptions) {},
63+ }),
64+ Entry("when the issuer URL is missing", &newProviderVerifierTableInput{
65+ modifyOpts: func(p *ProviderVerifierOptions) {
66+ p.IssuerURL = ""
67+ },
68+ expectedError: "invalid provider verifier options: missing required setting: issuer-url",
69+ }),
70+ Entry("when the issuer URL is invalid", &newProviderVerifierTableInput{
71+ modifyOpts: func(p *ProviderVerifierOptions) {
72+ p.IssuerURL = "invalid"
73+ },
74+ expectedError: "could not get verifier builder: error while discovery OIDC configuration: failed to discover OIDC configuration: error performing request: Get \"invalid/.well-known/openid-configuration\": unsupported protocol scheme \"\"",
75+ }),
76+ Entry("with skip discovery and the JWKs URL is missing", &newProviderVerifierTableInput{
77+ modifyOpts: func(p *ProviderVerifierOptions) {
78+ p.SkipDiscovery = true
79+ p.JWKsURL = ""
80+ },
81+ expectedError: "invalid provider verifier options: missing required setting: jwks-url",
82+ }),
83+ Entry("should be succesfful when skipping discovery with the JWKs URL specified", &newProviderVerifierTableInput{
84+ modifyOpts: func(p *ProviderVerifierOptions) {
85+ p.SkipDiscovery = true
86+ p.JWKsURL = m.JWKSEndpoint()
87+ },
88+ }),
89+ )
90+
91+ type verifierTableInput struct {
92+ modifyOpts func(*ProviderVerifierOptions)
93+ modifyClaims func(*jwt.StandardClaims)
94+ expectedError string
95+ }
96+
97+ DescribeTable("when constructing the provider verifier", func(in *verifierTableInput) {
98+ opts := ProviderVerifierOptions{
99+ AudienceClaims: []string{"aud"},
100+ ClientID: m.Config().ClientID,
101+ ExtraAudiences: []string{},
102+ IssuerURL: m.Issuer(),
103+ }
104+ if in.modifyOpts != nil {
105+ in.modifyOpts(&opts)
106+ }
107+
108+ pv, err := NewProviderVerifier(context.Background(), opts)
109+ Expect(err).ToNot(HaveOccurred())
110+
111+ now := time.Now()
112+ claims := jwt.StandardClaims{
113+ Audience: m.Config().ClientID,
114+ Issuer: m.Issuer(),
115+ ExpiresAt: now.Add(1 * time.Hour).Unix(),
116+ IssuedAt: now.Unix(),
117+ Subject: "user",
118+ }
119+ if in.modifyClaims != nil {
120+ in.modifyClaims(&claims)
121+ }
122+
123+ rawIDToken, err := m.Keypair.SignJWT(claims)
124+ Expect(err).ToNot(HaveOccurred())
125+
126+ idToken, err := pv.Verifier().Verify(context.Background(), rawIDToken)
127+ if in.expectedError != "" {
128+ Expect(err).To(MatchError(HavePrefix(in.expectedError)))
129+ return
130+ }
131+ Expect(err).ToNot(HaveOccurred())
132+
133+ Expect(idToken.Issuer).To(Equal(claims.Issuer))
134+ Expect(idToken.Audience).To(ConsistOf(claims.Audience))
135+ Expect(idToken.Subject).To(Equal(claims.Subject))
136+ },
137+ Entry("with the default opts and claims", &verifierTableInput{}),
138+ Entry("when the audience is mismatched", &verifierTableInput{
139+ modifyClaims: func(j *jwt.StandardClaims) {
140+ j.Audience = "OtherClient"
141+ },
142+ expectedError: "audience from claim aud with value [OtherClient] does not match with any of allowed audiences",
143+ }),
144+ Entry("when the audience is an extra audience", &verifierTableInput{
145+ modifyOpts: func(p *ProviderVerifierOptions) {
146+ p.ExtraAudiences = []string{"ExtraIssuer"}
147+ },
148+ modifyClaims: func(j *jwt.StandardClaims) {
149+ j.Audience = "ExtraIssuer"
150+ },
151+ }),
152+ Entry("when the issuer is mismatched", &verifierTableInput{
153+ modifyClaims: func(j *jwt.StandardClaims) {
154+ j.Issuer = "OtherIssuer"
155+ },
156+ expectedError: "failed to verify token: oidc: id token issued by a different provider",
157+ }),
158+ Entry("when the issuer is mismatched with skip issuer verification", &verifierTableInput{
159+ modifyOpts: func(p *ProviderVerifierOptions) {
160+ p.SkipIssuerVerification = true
161+ },
162+ modifyClaims: func(j *jwt.StandardClaims) {
163+ j.Issuer = "OtherIssuer"
164+ },
165+ }),
166+ Entry("when the token has expired", &verifierTableInput{
167+ modifyClaims: func(j *jwt.StandardClaims) {
168+ j.ExpiresAt = time.Now().Add(-1 * time.Hour).Unix()
169+ },
170+ expectedError: "failed to verify token: oidc: token is expired",
171+ }),
172+ )
173+})