@@ -1,237 +0,0 @@
1−package providers
2−
3−import (
4− "context"
5− "encoding/base64"
6− "encoding/json"
7− "net/http"
8− "net/http/httptest"
9− "net/url"
10− "testing"
11− "time"
12−
13− "github.com/golang-jwt/jwt/v5"
14− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
15− . "github.com/onsi/gomega"
16−)
17−
18−const (
19− dttSSOTestClientID = "redash-gate"
20− // Shorter than 32 bytes, and contains characters golang.org/x/oauth2 would
21− // URL-escape in Basic auth.
22− dttSSOTestSecret = "s3cr+t/abc=="
23−)
24−
25−// dttSSOTestServer mimics the SSO's token and check_token endpoints.
26−type dttSSOTestServer struct {
27− *httptest.Server
28− claims map[string]interface{}
29− signingKey []byte
30− lastForm url.Values
31− validTokens map[string]bool
32− tokenCounter int
33−}
34−
35−func newDTTSSOTestServer() *dttSSOTestServer {
36− s := &dttSSOTestServer{
37− signingKey: []byte(dttSSOTestSecret),
38− validTokens: map[string]bool{},
39− }
40− s.Server = httptest.NewServer(http.HandlerFunc(s.handle))
41− u, _ := url.Parse(s.URL)
42− s.claims = map[string]interface{}{
43− "iss": "http://" + u.Host,
44− "aud": dttSSOTestClientID,
45− "sub": "0b5c1e1a-1111-2222-3333-444455556666",
46− "user_id": "0b5c1e1a-1111-2222-3333-444455556666",
47− "email": "admin@example.com",
48− "role": "dtt_admin",
49− "exp": time.Now().Add(time.Hour).Unix(),
50− }
51− return s
52−}
53−
54−func (s *dttSSOTestServer) handle(w http.ResponseWriter, r *http.Request) {
55− switch r.URL.Path {
56− case "/oauth/token":
57− want := "Basic " + base64.StdEncoding.EncodeToString([]byte(dttSSOTestClientID+":"+dttSSOTestSecret))
58− if r.Header.Get("Authorization") != want {
59− w.WriteHeader(http.StatusUnauthorized)
60− return
61− }
62− _ = r.ParseForm()
63− s.lastForm = r.PostForm
64− if r.PostForm.Get("grant_type") == "refresh_token" && !s.validTokens["refresh-"+r.PostForm.Get("refresh_token")] {
65− w.WriteHeader(http.StatusBadRequest)
66− _, _ = w.Write([]byte(`{"error":"invalid_grant","error_description":"Invalid refresh token"}`))
67− return
68− }
69− s.tokenCounter++
70− access := "access-" + string(rune('0'+s.tokenCounter))
71− refresh := "refresh-" + string(rune('0'+s.tokenCounter))
72− s.validTokens[access] = true
73− s.validTokens["refresh-"+refresh] = true
74− w.Header().Set("Content-Type", "application/json")
75− _ = json.NewEncoder(w).Encode(map[string]interface{}{
76− "access_token": access,
77− "token_type": "bearer",
78− "expires_in": 3600,
79− "refresh_token": refresh,
80− "id_token": s.idToken(),
81− "scope": "openid",
82− })
83− case "/oauth/check_token":
84− token := r.Header.Get("Authorization")
85− if len(token) > len("Bearer ") && s.validTokens[token[len("Bearer "):]] {
86− _, _ = w.Write([]byte(`{"user_id":"0b5c1e1a-1111-2222-3333-444455556666","real_user_id":null,"issued_by":"redash-gate"}`))
87− return
88− }
89− w.WriteHeader(http.StatusUnauthorized)
90− _, _ = w.Write([]byte("Invalid access token"))
91− default:
92− w.WriteHeader(http.StatusNotFound)
93− }
94−}
95−
96−func (s *dttSSOTestServer) idToken() string {
97− if s.claims == nil {
98− return ""
99− }
100− raw, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(s.claims)).SignedString(s.signingKey)
101− if err != nil {
102− panic(err)
103− }
104− return raw
105−}
106−
107−func testDTTSSOProvider(server *dttSSOTestServer) *DTTSSOProvider {
108− p := NewDTTSSOProvider(&ProviderData{ClientID: dttSSOTestClientID, ClientSecret: dttSSOTestSecret})
109− u, _ := url.Parse(server.URL)
110− updateURL(p.LoginURL, u.Host)
111− updateURL(p.RedeemURL, u.Host)
112− updateURL(p.ValidateURL, u.Host)
113− return p
114−}
115−
116−func TestNewDTTSSOProvider(t *testing.T) {
117− g := NewWithT(t)
118−
119− providerData := NewDTTSSOProvider(&ProviderData{}).Data()
120− g.Expect(providerData.ProviderName).To(Equal("DTT SSO"))
121− g.Expect(providerData.LoginURL.String()).To(Equal("https://auth.thebestagent.pro/oauth/authorize"))
122− g.Expect(providerData.RedeemURL.String()).To(Equal("https://auth.thebestagent.pro/oauth/token"))
123− g.Expect(providerData.ValidateURL.String()).To(Equal("https://auth.thebestagent.pro/oauth/check_token"))
124− g.Expect(providerData.Scope).To(Equal("openid"))
125−}
126−
127−func TestDTTSSOProviderRedeem(t *testing.T) {
128− g := NewWithT(t)
129− server := newDTTSSOTestServer()
130− defer server.Close()
131− p := testDTTSSOProvider(server)
132−
133− s, err := p.Redeem(context.Background(), "https://redash.example.com/oauth2/callback", "the-code", "")
134− g.Expect(err).ToNot(HaveOccurred())
135− g.Expect(s.Email).To(Equal("admin@example.com"))
136− g.Expect(s.User).To(Equal("0b5c1e1a-1111-2222-3333-444455556666"))
137− g.Expect(s.Groups).To(Equal([]string{"dtt_admin"}))
138− g.Expect(s.AccessToken).To(Equal("access-1"))
139− g.Expect(s.RefreshToken).To(Equal("refresh-1"))
140− g.Expect(s.IDToken).ToNot(BeEmpty())
141− g.Expect(s.ExpiresOn).ToNot(BeNil())
142− g.Expect(*s.ExpiresOn).To(BeTemporally("~", time.Now().Add(time.Hour), time.Minute))
143−
144− g.Expect(server.lastForm.Get("grant_type")).To(Equal("authorization_code"))
145− g.Expect(server.lastForm.Get("code")).To(Equal("the-code"))
146− g.Expect(server.lastForm.Get("redirect_uri")).To(Equal("https://redash.example.com/oauth2/callback"))
147−}
148−
149−func TestDTTSSOProviderRedeemRejectsBadIDTokens(t *testing.T) {
150− testCases := map[string]func(s *dttSSOTestServer){
151− "signed with another secret": func(s *dttSSOTestServer) { s.signingKey = []byte("some-other-client-secret") },
152− "other audience": func(s *dttSSOTestServer) { s.claims["aud"] = "frappe-gate" },
153− "other issuer": func(s *dttSSOTestServer) { s.claims["iss"] = "https://evil.example.com" },
154− "expired": func(s *dttSSOTestServer) { s.claims["exp"] = time.Now().Add(-time.Minute).Unix() },
155− "no email": func(s *dttSSOTestServer) { delete(s.claims, "email") },
156− "no id_token": func(s *dttSSOTestServer) { s.claims = nil },
157− }
158− for name, tamper := range testCases {
159− t.Run(name, func(t *testing.T) {
160− g := NewWithT(t)
161− server := newDTTSSOTestServer()
162− defer server.Close()
163− tamper(server)
164−
165− _, err := testDTTSSOProvider(server).Redeem(context.Background(), "https://redash.example.com/oauth2/callback", "the-code", "")
166− g.Expect(err).To(HaveOccurred())
167− })
168− }
169−}
170−
171−func TestDTTSSOProviderRedeemRejectsUnsignedIDToken(t *testing.T) {
172− g := NewWithT(t)
173− server := newDTTSSOTestServer()
174− defer server.Close()
175− p := testDTTSSOProvider(server)
176−
177− header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none"}`))
178− payload, _ := json.Marshal(server.claims)
179− unsigned := header + "." + base64.RawURLEncoding.EncodeToString(payload) + "."
180−
181− _, err := p.verifyIDToken(unsigned, dttSSOTestSecret)
182− g.Expect(err).To(HaveOccurred())
183−}
184−
185−func TestDTTSSOProviderRefreshSession(t *testing.T) {
186− g := NewWithT(t)
187− server := newDTTSSOTestServer()
188− defer server.Close()
189− p := testDTTSSOProvider(server)
190−
191− s, err := p.Redeem(context.Background(), "https://redash.example.com/oauth2/callback", "the-code", "")
192− g.Expect(err).ToNot(HaveOccurred())
193− nonce := []byte("keep-me")
194− s.Nonce = nonce
195−
196− server.claims["role"] = "agent"
197− server.claims["email"] = "renamed@example.com"
198− refreshed, err := p.RefreshSession(context.Background(), s)
199− g.Expect(err).ToNot(HaveOccurred())
200− g.Expect(refreshed).To(BeTrue())
201− g.Expect(s.AccessToken).To(Equal("access-2"))
202− g.Expect(s.RefreshToken).To(Equal("refresh-2"))
203− g.Expect(s.Groups).To(Equal([]string{"agent"}))
204− g.Expect(s.Email).To(Equal("renamed@example.com"))
205− g.Expect(s.Nonce).To(Equal(nonce))
206− g.Expect(server.lastForm.Get("grant_type")).To(Equal("refresh_token"))
207− g.Expect(server.lastForm.Get("refresh_token")).To(Equal("refresh-1"))
208−}
209−
210−func TestDTTSSOProviderRefreshSessionFailures(t *testing.T) {
211− g := NewWithT(t)
212− server := newDTTSSOTestServer()
213− defer server.Close()
214− p := testDTTSSOProvider(server)
215−
216− refreshed, err := p.RefreshSession(context.Background(), &sessions.SessionState{})
217− g.Expect(err).ToNot(HaveOccurred())
218− g.Expect(refreshed).To(BeFalse())
219−
220− refreshed, err = p.RefreshSession(context.Background(), &sessions.SessionState{RefreshToken: "revoked"})
221− g.Expect(err).To(HaveOccurred())
222− g.Expect(refreshed).To(BeFalse())
223−}
224−
225−func TestDTTSSOProviderValidateSession(t *testing.T) {
226− g := NewWithT(t)
227− server := newDTTSSOTestServer()
228− defer server.Close()
229− p := testDTTSSOProvider(server)
230−
231− s, err := p.Redeem(context.Background(), "https://redash.example.com/oauth2/callback", "the-code", "")
232− g.Expect(err).ToNot(HaveOccurred())
233− g.Expect(p.ValidateSession(context.Background(), s)).To(BeTrue())
234−
235− delete(server.validTokens, s.AccessToken)
236− g.Expect(p.ValidateSession(context.Background(), s)).To(BeFalse())
237−}