@@ -0,0 +1,205 @@
1+package providers
2+
3+import (
4+ "context"
5+ "encoding/base64"
6+ "net/http"
7+ "net/http/httptest"
8+ "net/url"
9+ "strings"
10+
11+ "github.com/coreos/go-oidc"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
13+ . "github.com/onsi/ginkgo"
14+ . "github.com/onsi/ginkgo/extensions/table"
15+ . "github.com/onsi/gomega"
16+)
17+
18+type fakeADFSJwks struct{}
19+
20+func (fakeADFSJwks) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
21+ decodeString, err := base64.RawURLEncoding.DecodeString(strings.Split(jwt, ".")[1])
22+ if err != nil {
23+ return nil, err
24+ }
25+ return decodeString, nil
26+}
27+
28+func testADFSProvider(hostname string) *ADFSProvider {
29+
30+ o := oidc.NewVerifier(
31+ "https://issuer.example.com",
32+ fakeADFSJwks{},
33+ &oidc.Config{ClientID: "https://test.myapp.com"},
34+ )
35+
36+ p := NewADFSProvider(&ProviderData{
37+ ProviderName: "",
38+ LoginURL: &url.URL{},
39+ RedeemURL: &url.URL{},
40+ ProfileURL: &url.URL{},
41+ ValidateURL: &url.URL{},
42+ Scope: "",
43+ Verifier: o,
44+ })
45+
46+ if hostname != "" {
47+ updateURL(p.Data().LoginURL, hostname)
48+ updateURL(p.Data().RedeemURL, hostname)
49+ updateURL(p.Data().ProfileURL, hostname)
50+ updateURL(p.Data().ValidateURL, hostname)
51+ }
52+
53+ return p
54+}
55+
56+func testADFSBackend() *httptest.Server {
57+
58+ authResponse := `
59+ {
60+ "access_token": "my_access_token",
61+ "id_token": "my_id_token",
62+ "refresh_token": "my_refresh_token"
63+ }
64+ `
65+ userInfo := `
66+ {
67+ "email": "samiracho@email.com"
68+ }
69+ `
70+
71+ refreshResponse := `{ "access_token": "new_some_access_token", "refresh_token": "new_some_refresh_token", "expires_in": "32693148245", "id_token": "new_some_id_token" }`
72+
73+ authHeader := "Bearer adfs_access_token"
74+
75+ return httptest.NewServer(http.HandlerFunc(
76+ func(w http.ResponseWriter, r *http.Request) {
77+ switch r.URL.Path {
78+ case "/adfs/oauth2/authorize":
79+ w.WriteHeader(200)
80+ w.Write([]byte(authResponse))
81+ case "/adfs/oauth2/refresh":
82+ w.WriteHeader(200)
83+ w.Write([]byte(refreshResponse))
84+ case "/adfs/oauth2/userinfo":
85+ if r.Header["Authorization"][0] == authHeader {
86+ w.WriteHeader(200)
87+ w.Write([]byte(userInfo))
88+ } else {
89+ w.WriteHeader(401)
90+ }
91+ default:
92+ w.WriteHeader(200)
93+ }
94+ }))
95+}
96+
97+var _ = Describe("ADFS Provider Tests", func() {
98+ var p *ADFSProvider
99+ var b *httptest.Server
100+
101+ BeforeEach(func() {
102+ b = testADFSBackend()
103+
104+ bURL, err := url.Parse(b.URL)
105+ Expect(err).To(BeNil())
106+
107+ p = testADFSProvider(bURL.Host)
108+ })
109+
110+ AfterEach(func() {
111+ b.Close()
112+ })
113+
114+ Context("New Provider Init", func() {
115+ It("uses defaults", func() {
116+ providerData := NewADFSProvider(&ProviderData{}).Data()
117+ Expect(providerData.ProviderName).To(Equal("ADFS"))
118+ Expect(providerData.Scope).To(Equal("openid email profile"))
119+ })
120+ })
121+
122+ Context("with bad token", func() {
123+ It("should trigger an error", func() {
124+ session := &sessions.SessionState{AccessToken: "unexpected_adfs_access_token", IDToken: "malformed_token"}
125+ err := p.EnrichSession(context.Background(), session)
126+ Expect(err).NotTo(BeNil())
127+ })
128+ })
129+
130+ Context("with valid token", func() {
131+ It("should not throw an error", func() {
132+ p.EmailClaim = "email"
133+ rawIDToken, _ := newSignedTestIDToken(defaultIDToken)
134+ idToken, err := p.Verifier.Verify(context.Background(), rawIDToken)
135+ Expect(err).To(BeNil())
136+ session, err := p.buildSessionFromClaims(idToken)
137+ session.IDToken = rawIDToken
138+ Expect(err).To(BeNil())
139+ err = p.EnrichSession(context.Background(), session)
140+ Expect(session.Email).To(Equal("janed@me.com"))
141+ Expect(err).To(BeNil())
142+ })
143+ })
144+
145+ Context("with skipScope enabled", func() {
146+ It("should not include parameter scope", func() {
147+ resource, _ := url.Parse("http://example.com")
148+ p := NewADFSProvider(&ProviderData{
149+ ProtectedResource: resource,
150+ Scope: "",
151+ })
152+ p.SkipScope = true
153+
154+ result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "")
155+ Expect(result).NotTo(ContainSubstring("scope="))
156+ })
157+ })
158+
159+ Context("With resource parameter", func() {
160+ type scopeTableInput struct {
161+ resource string
162+ scope string
163+ expectedScope string
164+ }
165+
166+ DescribeTable("should return expected results",
167+ func(in scopeTableInput) {
168+ resource, _ := url.Parse(in.resource)
169+ p := NewADFSProvider(&ProviderData{
170+ ProtectedResource: resource,
171+ Scope: in.scope,
172+ })
173+
174+ Expect(p.Data().Scope).To(Equal(in.expectedScope))
175+ result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "")
176+ Expect(result).To(ContainSubstring("scope=" + url.QueryEscape(in.expectedScope)))
177+ },
178+ Entry("should add slash", scopeTableInput{
179+ resource: "http://resource.com",
180+ scope: "openid",
181+ expectedScope: "http://resource.com/openid",
182+ }),
183+ Entry("shouldn't add extra slash", scopeTableInput{
184+ resource: "http://resource.com/",
185+ scope: "openid",
186+ expectedScope: "http://resource.com/openid",
187+ }),
188+ Entry("should add default scopes with resource", scopeTableInput{
189+ resource: "http://resource.com/",
190+ scope: "",
191+ expectedScope: "http://resource.com/openid email profile",
192+ }),
193+ Entry("should add default scopes", scopeTableInput{
194+ resource: "",
195+ scope: "",
196+ expectedScope: "openid email profile",
197+ }),
198+ Entry("shouldn't add resource if already in scopes", scopeTableInput{
199+ resource: "http://resource.com",
200+ scope: "http://resource.com/openid",
201+ expectedScope: "http://resource.com/openid",
202+ }),
203+ )
204+ })
205+})