From 8e211ff4bac177465fb9adc0bfa3744ca4e1da47 Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 15 May 2025 09:12:22 -0600 Subject: refactor: delete code that is now handled by envoy --- app/controllers/sessions/controller.go | 130 -------------- app/controllers/sessions/controller_test.go | 253 ---------------------------- app/controllers/sessions/dto.go | 19 --- app/controllers/sessions/service.go | 80 --------- app/controllers/sessions/service_test.go | 92 ---------- 5 files changed, 574 deletions(-) delete mode 100644 app/controllers/sessions/controller.go delete mode 100644 app/controllers/sessions/controller_test.go delete mode 100644 app/controllers/sessions/dto.go delete mode 100644 app/controllers/sessions/service.go delete mode 100644 app/controllers/sessions/service_test.go (limited to 'app/controllers') diff --git a/app/controllers/sessions/controller.go b/app/controllers/sessions/controller.go deleted file mode 100644 index 898244c..0000000 --- a/app/controllers/sessions/controller.go +++ /dev/null @@ -1,130 +0,0 @@ -package sessions - -import ( - "net/http" - - "github.com/xlgmokha/x/pkg/cookie" - "github.com/xlgmokha/x/pkg/env" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/middleware" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" -) - -type Controller struct { - svc *Service -} - -func New(cfg *oidc.OpenID, http *http.Client) *Controller { - return &Controller{ - svc: NewService(cfg, http), - } -} - -func (c *Controller) MountTo(mux *http.ServeMux) { - mux.HandleFunc("GET /session/new", c.New) - mux.HandleFunc("GET /session", c.Show) - mux.HandleFunc("GET /session/callback", c.Create) - mux.HandleFunc("POST /session/destroy", c.Destroy) -} - -func (c *Controller) New(w http.ResponseWriter, r *http.Request) { - if middleware.IsLoggedIn(r) { - http.Redirect(w, r, "/dashboard", http.StatusFound) - return - } - - url, nonce := c.svc.GenerateRedirectURL(r) - cookie.Write(w, web.NewCookie(cfg.CSRFCookie, nonce)) - c.redirectTo(w, r, url) -} - -func (c *Controller) Show(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(cfg.IDTokenCookie) - if err != nil { - pls.LogError(r.Context(), err) - w.WriteHeader(http.StatusNotFound) - return - } - - token, err := c.svc.JWTBody(r.Context(), oidc.RawToken(cookie.Value)) - if err != nil { - pls.LogError(r.Context(), err) - w.WriteHeader(http.StatusNotFound) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(token) -} - -/* -This is the callback endpoint from the OIDC Provider: - -It will exchange a grant for the following tokens: - -* access_token -* id_token -* refresh_token - -# These tokens are encoded as a Base64 JSON string and stored in a session cookie - -For Example: - -The following is an example of the base64 value stored in the session cookie: - -```base64 -eyJhY2Nlc3NfdG9rZW4iOiJkNzQ2ZTVmMGQ2NmYyNTgxM2ZjYzIyYzNjY2E0YmYxYjNmOTQwMjQ5NTkxYTg4YzZmMDBjMDQzZTMxYTVkZDRhIiwidG9rZW5fdHlwZSI6IkJlYXJlciIsInJlZnJlc2hfdG9rZW4iOiIyZGUxNTFhMGQ2ZGJhNzU5MDVmNGRmZTRlZThkNmQ5MGNjZmRkYzYxYjJkNGRlNzU2ODRiYzFjYmY5YjE0ODBlIiwiZXhwaXJ5IjoiMjAyNS0wNC0yNVQxMTozODozOS4zNDAwNDc4MjQtMDY6MDAiLCJpZF90b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0pyYVdRaU9pSjBaREJUYldSS1VUUnhVR2cxY1U1TGVrMHlOakJEV0hneVZXZ3RkMmhITFUxRWFtOVBTMWRtZERoRklpd2lZV3huSWpvaVVsTXlOVFlpZlEuZXlKcGMzTWlPaUpvZEhSd09pOHZaMlJyTG5SbGMzUTZNekF3TUNJc0luTjFZaUk2SWpFaUxDSmhkV1FpT2lKbE16RmxNV1JoTUdJNFpqWmlObVV6TldOaE56QmpOemt3WWpFell6QTBNRFpsTkRSaFkyRTJZakppWmpZM1pqVTFaR1UzTXpVMVlUazNPV0V5TWpSbUlpd2laWGh3SWpveE56UTFOVGsxTmpNNUxDSnBZWFFpT2pFM05EVTFPVFUxTVRrc0ltRjFkR2hmZEdsdFpTSTZNVGMwTlRVd016TXpPQ3dpYzNWaVgyeGxaMkZqZVNJNklqSTBOelJqWmpCaU1qSXhNVFk0T0dFMU56STVOMkZqWlRCbE1qWXdZVEUxT1RRME56VTBaREUyWWpGaVpEUXlZemxrTmpjM09XTTVNREF6TmpjNE1EY2lMQ0p1WVcxbElqb2lRV1J0YVc1cGMzUnlZWFJ2Y2lJc0ltNXBZMnR1WVcxbElqb2ljbTl2ZENJc0luQnlaV1psY25KbFpGOTFjMlZ5Ym1GdFpTSTZJbkp2YjNRaUxDSmxiV0ZwYkNJNkltRmtiV2x1UUdWNFlXMXdiR1V1WTI5dElpd2laVzFoYVd4ZmRtVnlhV1pwWldRaU9uUnlkV1VzSW5CeWIyWnBiR1VpT2lKb2RIUndPaTh2WjJSckxuUmxjM1E2TXpBd01DOXliMjkwSWl3aWNHbGpkSFZ5WlNJNkltaDBkSEJ6T2k4dmQzZDNMbWR5WVhaaGRHRnlMbU52YlM5aGRtRjBZWEl2TWpVNFpEaGtZemt4Tm1SaU9HTmxZVEpqWVdaaU5tTXpZMlF3WTJJd01qUTJaV1psTURZeE5ESXhaR0prT0RObFl6TmhNelV3TkRJNFkyRmlaR0UwWmo5elBUZ3dKbVE5YVdSbGJuUnBZMjl1SWl3aVozSnZkWEJ6WDJScGNtVmpkQ0k2V3lKbmFYUnNZV0l0YjNKbklpd2lkRzl2YkdKdmVDSXNJbTFoYzNOZmFXNXpaWEowWDJkeWIzVndYMTh3WHpFd01DSXNJbU4xYzNSdmJTMXliMnhsY3kxeWIyOTBMV2R5YjNWd0wyRmhJaXdpWTNWemRHOXRMWEp2YkdWekxYSnZiM1F0WjNKdmRYQXZZV0V2WVdGaElpd2laMjUxZDJkbGRDSXNJa052YlcxcGREUTFNU0lzSW1waGMyaHJaVzVoY3lJc0ltWnNhV2RvZEdweklpd2lkSGRwZEhSbGNpSXNJbWRwZEd4aFlpMWxlR0Z0Y0d4bGN5SXNJbWRwZEd4aFlpMWxlR0Z0Y0d4bGN5OXpaV04xY21sMGVTSXNJalF4TWpjd09DSXNJbWRwZEd4aFlpMWxlR0Z0Y0d4bGN5OWtaVzF2TFdkeWIzVndJaXdpWTNWemRHOXRMWEp2YkdWekxYSnZiM1F0WjNKdmRYQWlMQ0kwTXpRd05EUXRaM0p2ZFhBdE1TSXNJalF6TkRBME5DMW5jbTkxY0MweUlpd2laMmwwYkdGaUxXOXlaekVpTENKbmFYUnNZV0l0YjNKbkwzTmxZM1Z5WlNJc0ltZHBkR3hoWWkxdmNtY3ZjMlZqZFhKbEwyMWhibUZuWlhKeklpd2laMmwwYkdGaUxXOXlaeTl6WldOMWNtbDBlUzF3Y205a2RXTjBjeUlzSW1kcGRHeGhZaTF2Y21jdmMyVmpkWEpwZEhrdGNISnZaSFZqZEhNdllXNWhiSGw2WlhKeklsMTkuZ2Fwc01vcVJSOWZyVS1MQTVYaUtaMC1PYWVkNG1SSXNiOG5JbEJuVUswM1lTVTY4R2Y5WlhLV1F0VHMwbGpCekNxLWZhOXBWY0I5YU1TUnZ0bGJuZ0doNFU0aWpWUGU0am5vWC01VXJZaTJpTEYxdDJ5VGRFWEhDSWg2bXNBLXJEUTUwR2UxNUtaSmRXTE0tbFo0VGhNNENlbGpQMWF4NUJjeUV0UG1pcmZIaHppR3pKYmFEczRVMk5aaW1hcHo3Q1hSX3FaeHI0ajYyQW00dmVYXzhPaDFhT1I2bUtDMTlCZUlqeFozWlZ3Z0x3UUVsaFlLcEhUWTRSS2ZnUkh3TVlXVGZIZUF0VTM4UTV0VW9DSGU0RW1wcEIza0x0aW1Gemp2YWhnMGRjazBzc3FTWkh4X252cXJldjctSVdKa096OVRSVG04SU1xU3h4OUxxd1pCVFRRIn0= -``` - -When it is decoded it has the following form: - -```json - - { - "access_token": "d746e5f0d66f25813fcc22c3cca4bf1b3f940249591a88c6f00c043e31a5dd4a", - "token_type": "Bearer", - "refresh_token": "2de151a0d6dba75905f4dfe4ee8d6d90ccfddc61b2d4de75684bc1cbf9b1480e", - "expiry": "2025-04-25T11:38:39.340047824-06:00", - "id_token": "eyJ0eXAiOiJKV1QiLCJraWQiOiJ0ZDBTbWRKUTRxUGg1cU5Lek0yNjBDWHgyVWgtd2hHLU1Eam9PS1dmdDhFIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMCIsInN1YiI6IjEiLCJhdWQiOiJlMzFlMWRhMGI4ZjZiNmUzNWNhNzBjNzkwYjEzYzA0MDZlNDRhY2E2YjJiZjY3ZjU1ZGU3MzU1YTk3OWEyMjRmIiwiZXhwIjoxNzQ1NTk1NjM5LCJpYXQiOjE3NDU1OTU1MTksImF1dGhfdGltZSI6MTc0NTUwMzMzOCwic3ViX2xlZ2FjeSI6IjI0NzRjZjBiMjIxMTY4OGE1NzI5N2FjZTBlMjYwYTE1OTQ0NzU0ZDE2YjFiZDQyYzlkNjc3OWM5MDAzNjc4MDciLCJuYW1lIjoiQWRtaW5pc3RyYXRvciIsIm5pY2tuYW1lIjoicm9vdCIsInByZWZlcnJlZF91c2VybmFtZSI6InJvb3QiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByb2ZpbGUiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMC9yb290IiwicGljdHVyZSI6Imh0dHBzOi8vd3d3LmdyYXZhdGFyLmNvbS9hdmF0YXIvMjU4ZDhkYzkxNmRiOGNlYTJjYWZiNmMzY2QwY2IwMjQ2ZWZlMDYxNDIxZGJkODNlYzNhMzUwNDI4Y2FiZGE0Zj9zPTgwJmQ9aWRlbnRpY29uIiwiZ3JvdXBzX2RpcmVjdCI6WyJnaXRsYWItb3JnIiwidG9vbGJveCIsIm1hc3NfaW5zZXJ0X2dyb3VwX18wXzEwMCIsImN1c3RvbS1yb2xlcy1yb290LWdyb3VwL2FhIiwiY3VzdG9tLXJvbGVzLXJvb3QtZ3JvdXAvYWEvYWFhIiwiZ251d2dldCIsIkNvbW1pdDQ1MSIsImphc2hrZW5hcyIsImZsaWdodGpzIiwidHdpdHRlciIsImdpdGxhYi1leGFtcGxlcyIsImdpdGxhYi1leGFtcGxlcy9zZWN1cml0eSIsIjQxMjcwOCIsImdpdGxhYi1leGFtcGxlcy9kZW1vLWdyb3VwIiwiY3VzdG9tLXJvbGVzLXJvb3QtZ3JvdXAiLCI0MzQwNDQtZ3JvdXAtMSIsIjQzNDA0NC1ncm91cC0yIiwiZ2l0bGFiLW9yZzEiLCJnaXRsYWItb3JnL3NlY3VyZSIsImdpdGxhYi1vcmcvc2VjdXJlL21hbmFnZXJzIiwiZ2l0bGFiLW9yZy9zZWN1cml0eS1wcm9kdWN0cyIsImdpdGxhYi1vcmcvc2VjdXJpdHktcHJvZHVjdHMvYW5hbHl6ZXJzIl19.gapsMoqRR9frU-LA5XiKZ0-Oaed4mRIsb8nIlBnUK03YSU68Gf9ZXKWQtTs0ljBzCq-fa9pVcB9aMSRvtlbngGh4U4ijVPe4jnoX-5UrYi2iLF1t2yTdEXHCIh6msA-rDQ50Ge15KZJdWLM-lZ4ThM4CeljP1ax5BcyEtPmirfHhziGzJbaDs4U2NZimapz7CXR_qZxr4j62Am4veX_8Oh1aOR6mKC19BeIjxZ3ZVwgLwQElhYKpHTY4RKfgRHwMYWTfHeAtU38Q5tUoCHe4EmppB3kLtimFzjvahg0dck0ssqSZHx_nvqrev7-IWJkOz9TRTm8IMqSxx9LqwZBTTQ" - } - -``` -*/ -func (c *Controller) Create(w http.ResponseWriter, r *http.Request) { - if middleware.IsLoggedIn(r) { - http.Redirect(w, r, "/dashboard", http.StatusFound) - return - } - - tokens, err := c.svc.Exchange(r) - if err != nil { - pls.LogError(r.Context(), err) - w.WriteHeader(http.StatusBadRequest) - return - } - - web.ExpireCookie(w, cfg.CSRFCookie) - web.WriteCookie(w, web.NewCookie(cfg.IDTokenCookie, tokens.IDToken.String())) - web.WriteCookie(w, web.NewCookie(cfg.BearerTokenCookie, tokens.AccessToken)) - web.WriteCookie(w, web.NewCookie(cfg.RefreshTokenCookie, tokens.RefreshToken)) - - c.redirectTo(w, r, "/dashboard") -} - -func (c *Controller) Destroy(w http.ResponseWriter, r *http.Request) { - web.ExpireCookie(w, cfg.CSRFCookie) - web.ExpireCookie(w, cfg.IDTokenCookie) - web.ExpireCookie(w, cfg.BearerTokenCookie) - web.ExpireCookie(w, cfg.RefreshTokenCookie) - c.redirectTo(w, r, "/") -} - -func (c *Controller) redirectTo(w http.ResponseWriter, r *http.Request, location string) { - if env.Fetch("APP_ENV", "development") == "production" { - (&RedirectDTO{URL: location}).ServeHTTP(w, r) - } else { - http.Redirect(w, r, location, http.StatusFound) - } -} diff --git a/app/controllers/sessions/controller_test.go b/app/controllers/sessions/controller_test.go deleted file mode 100644 index 3e7f662..0000000 --- a/app/controllers/sessions/controller_test.go +++ /dev/null @@ -1,253 +0,0 @@ -package sessions - -import ( - "net/http" - "net/url" - "testing" - - "github.com/oauth2-proxy/mockoidc" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xlgmokha/x/pkg/serde" - "github.com/xlgmokha/x/pkg/test" - "github.com/xlgmokha/x/pkg/x" - xcfg "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" -) - -func TestSessions(t *testing.T) { - srv := oidc.NewTestServer(t) - defer srv.Close() - - clientID := srv.MockOIDC.Config().ClientID - clientSecret := srv.MockOIDC.Config().ClientSecret - cfg := oidc.New( - srv.Provider, - clientID, - clientSecret, - "callback_url", - ) - controller := New(cfg, http.DefaultClient) - mux := http.NewServeMux() - controller.MountTo(mux) - - t.Run("GET /session/new", func(t *testing.T) { - t.Run("without an authenticated session", func(t *testing.T) { - r, w := test.RequestResponse("GET", "/session/new") - - mux.ServeHTTP(w, r) - - t.Run("redirect to the OIDC Provider", func(t *testing.T) { - require.Equal(t, http.StatusFound, w.Code) - require.NotEmpty(t, w.Header().Get("Location")) - redirectURL, err := url.Parse(w.Header().Get("Location")) - require.NoError(t, err) - assert.Equal(t, srv.AuthorizationEndpoint(), redirectURL.Scheme+"://"+redirectURL.Host+redirectURL.Path) - assert.NotEmpty(t, redirectURL.Query().Get("state")) - assert.Equal(t, srv.MockOIDC.Config().ClientID, redirectURL.Query().Get("client_id")) - assert.Equal(t, "openid profile email", redirectURL.Query().Get("scope")) - assert.Equal(t, cfg.Config.ClientID, redirectURL.Query().Get("audience")) - assert.Equal(t, cfg.Config.RedirectURL, redirectURL.Query().Get("redirect_uri")) - assert.Equal(t, "code", redirectURL.Query().Get("response_type")) - }) - - t.Run("generates a CSRF token", func(t *testing.T) { - cookieHeader := w.Header().Get("Set-Cookie") - require.NotEmpty(t, cookieHeader) - - cookie, err := http.ParseSetCookie(w.Header().Get("Set-Cookie")) - require.NoError(t, err) - require.NotZero(t, cookie) - - assert.Equal(t, xcfg.CSRFCookie, cookie.Name) - }) - }) - - t.Run("with an active authenicated session", func(t *testing.T) { - t.Run("redirects to the dashboard", func(t *testing.T) { - user := &domain.User{} - r, w := test.RequestResponse( - "GET", - "/session/new", - test.WithContextKeyValue(t.Context(), xcfg.CurrentUser, user), - ) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusFound, w.Code) - assert.Equal(t, "/dashboard", w.Header().Get("Location")) - }) - }) - }) - - t.Run("GET /session/callback", func(t *testing.T) { - t.Run("with an invalid csrf token", func(t *testing.T) { - user := mockoidc.DefaultUser() - code := srv.CreateAuthorizationCodeFor(user) - nonce := pls.GenerateRandomHex(32) - - r, w := test.RequestResponse( - "GET", - "/session/callback?code="+code+"&state=invalid", - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusBadRequest, w.Code) - }) - - t.Run("with an invalid authorization code grant", func(t *testing.T) { - r, w := test.RequestResponse("GET", "/session/callback?code=invalid") - - mux.ServeHTTP(w, r) - - assert.Equal(t, http.StatusBadRequest, w.Code) - }) - - t.Run("when already logged in", func(t *testing.T) { - t.Run("redirects to the dashboard", func(t *testing.T) { - user := &domain.User{} - r, w := test.RequestResponse( - "GET", - "/session/callback?code=valid", - test.WithContextKeyValue(t.Context(), xcfg.CurrentUser, user), - ) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusFound, w.Code) - assert.Equal(t, "/dashboard", w.Header().Get("Location")) - }) - }) - - t.Run("with a valid authorization code grant", func(t *testing.T) { - user := mockoidc.DefaultUser() - code := srv.CreateAuthorizationCodeFor(user) - nonce := pls.GenerateRandomHex(32) - - r, w := test.RequestResponse( - "GET", - "/session/callback?code="+code+"&state="+nonce, - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - - mux.ServeHTTP(w, r) - - cookieValues := w.Header().Values("Set-Cookie") - cookies := x.Map(cookieValues, func(line string) *http.Cookie { - ck, err := http.ParseSetCookie(line) - require.NoError(t, err) - return ck - }) - - t.Run("stores the id token in a session cookie", func(t *testing.T) { - cookie := x.Find(cookies, func(item *http.Cookie) bool { - return item.Name == xcfg.IDTokenCookie - }) - - require.NotNil(t, cookie) - - idToken := srv.Verify(cookie.Value) - assert.Equal(t, user.Subject, idToken.Subject) - }) - - t.Run("stores the access token in a session cookie", func(t *testing.T) { - cookie := x.Find(cookies, func(item *http.Cookie) bool { - return item.Name == xcfg.BearerTokenCookie - }) - - require.NotNil(t, cookie) - - keypair, err := mockoidc.DefaultKeypair() - require.NoError(t, err) - - token, err := keypair.VerifyJWT(cookie.Value, nil) - require.NoError(t, err) - - sub, err := token.Claims.GetSubject() - require.NoError(t, err) - assert.Equal(t, user.Subject, sub) - }) - - t.Run("stores the refresh token in a session cookie", func(t *testing.T) { - cookie := x.Find(cookies, func(item *http.Cookie) bool { - return item.Name == xcfg.RefreshTokenCookie - }) - require.NotNil(t, cookie) - - keypair, err := mockoidc.DefaultKeypair() - require.NoError(t, err) - - token, err := keypair.VerifyJWT(cookie.Value, nil) - require.NoError(t, err) - - sub, err := token.Claims.GetSubject() - require.NoError(t, err) - assert.Equal(t, user.Subject, sub) - }) - - t.Run("redirects to the homepage", func(t *testing.T) { - require.Equal(t, http.StatusFound, w.Code) - assert.Equal(t, "/dashboard", w.Header().Get("Location")) - }) - - t.Run("applies the appropriate cookie settings", func(t *testing.T) { - x.Each(cookies, func(cookie *http.Cookie) { - t.Logf("%v: %v\n", cookie.Name, cookie.Value) - assert.Equal(t, "/", cookie.Path) - assert.NotEmpty(t, cookie.Name) - assert.True(t, cookie.HttpOnly) - assert.True(t, cookie.Secure) - }) - }) - }) - }) - - t.Run("GET /session", func(t *testing.T) { - t.Run("with an id_token cookie", func(t *testing.T) { - user := mockoidc.DefaultUser() - _, rawIDToken := srv.CreateTokensFor(user) - cookie := &http.Cookie{Name: xcfg.IDTokenCookie, Value: rawIDToken} - r, w := test.RequestResponse("GET", "/session", test.WithCookie(cookie)) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusOK, w.Code) - items, err := serde.FromJSON[map[string]interface{}](w.Body) - require.NoError(t, err) - assert.Equal(t, srv.Issuer(), items["iss"]) - }) - - t.Run("without an id_token cookie", func(t *testing.T) { - r, w := test.RequestResponse("GET", "/session") - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusNotFound, w.Code) - }) - }) - - t.Run("POST /session/destroy", func(t *testing.T) { - t.Run("clears the session cookie", func(t *testing.T) { - cookie := web.NewCookie(xcfg.IDTokenCookie, "value") - r, w := test.RequestResponse("POST", "/session/destroy", test.WithCookie(cookie)) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusFound, w.Code) - assert.Equal(t, "/", w.Header().Get("Location")) - - expected := []string{ - "__csrf=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; HttpOnly; Secure", - "id_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; HttpOnly; Secure", - "bearer_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; HttpOnly; Secure", - "refresh_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; HttpOnly; Secure", - } - assert.ElementsMatch(t, expected, w.Header().Values("Set-Cookie")) - }) - }) -} diff --git a/app/controllers/sessions/dto.go b/app/controllers/sessions/dto.go deleted file mode 100644 index a3ce6ba..0000000 --- a/app/controllers/sessions/dto.go +++ /dev/null @@ -1,19 +0,0 @@ -package sessions - -import ( - "net/http" - - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/views" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" -) - -type RedirectDTO struct { - URL string -} - -func (d *RedirectDTO) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("Content-Type", "text/html") - - pls.LogError(r.Context(), views.Render(w, "sessions/redirect", d)) -} diff --git a/app/controllers/sessions/service.go b/app/controllers/sessions/service.go deleted file mode 100644 index c0466e4..0000000 --- a/app/controllers/sessions/service.go +++ /dev/null @@ -1,80 +0,0 @@ -package sessions - -import ( - "context" - "encoding/base64" - "errors" - "net/http" - "net/url" - "strings" - - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" - "golang.org/x/oauth2" -) - -type Service struct { - cfg *oidc.OpenID - http *http.Client -} - -func NewService(cfg *oidc.OpenID, http *http.Client) *Service { - return &Service{ - cfg: cfg, - http: http, - } -} - -func (svc *Service) GenerateRedirectURL(r *http.Request) (string, string) { - nonce := pls.GenerateRandomHex(32) - url := svc.cfg.Config.AuthCodeURL( - nonce, - oauth2.SetAuthURLParam("audience", svc.cfg.Config.ClientID), - oauth2.SetAuthURLParam("redirect_uri", svc.redirectURIFor(r)), - ) - return url, nonce -} - -func (svc *Service) Exchange(r *http.Request) (*oidc.Tokens, error) { - cookies := r.CookiesNamed(cfg.CSRFCookie) - if len(cookies) != 1 { - return nil, errors.New("Missing CSRF token") - } - - state := r.URL.Query().Get("state") - if state != cookies[0].Value { - return nil, errors.New("Invalid CSRF token") - } - - ctx := context.WithValue(r.Context(), oauth2.HTTPClient, svc.http) - - token, err := svc.cfg.Config.Exchange(ctx, r.URL.Query().Get("code")) - if err != nil { - return nil, err - } - - tokens := &oidc.Tokens{Token: token} - if rawIDToken, ok := token.Extra("id_token").(string); ok { - tokens.IDToken = oidc.RawToken(rawIDToken) - } - return tokens, nil -} - -func (svc *Service) JWTBody(ctx context.Context, raw oidc.RawToken) ([]byte, error) { - sections := strings.SplitN(raw.String(), ".", 3) - if len(sections) != 3 { - return nil, errors.New("Invalid JWT") - } - - return base64.RawURLEncoding.DecodeString(sections[1]) -} - -func (svc *Service) redirectURIFor(r *http.Request) string { - if len(svc.cfg.Config.RedirectURL) > 0 { - return svc.cfg.Config.RedirectURL - } - redirectURL, _ := url.Parse(r.URL.String()) - redirectURL.Path = "/session/callback" - return redirectURL.String() -} diff --git a/app/controllers/sessions/service_test.go b/app/controllers/sessions/service_test.go deleted file mode 100644 index 05baa2f..0000000 --- a/app/controllers/sessions/service_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package sessions - -import ( - "net/http" - "testing" - - "github.com/oauth2-proxy/mockoidc" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xlgmokha/x/pkg/test" - xcfg "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" -) - -func TestService(t *testing.T) { - srv := oidc.NewTestServer(t) - defer srv.Close() - - clientID := srv.MockOIDC.Config().ClientID - clientSecret := srv.MockOIDC.Config().ClientSecret - cfg := oidc.New( - srv.Provider, - clientID, - clientSecret, - "/session/callback", - ) - svc := NewService(cfg, http.DefaultClient) - - t.Run("Exchange", func(t *testing.T) { - t.Run("when the csrf token is missing", func(t *testing.T) { - r := test.Request("GET", "/session/callback") - tokens, err := svc.Exchange(r) - - require.Error(t, err) - assert.Nil(t, tokens) - }) - - t.Run("when the csrf token is invalid", func(t *testing.T) { - user := mockoidc.DefaultUser() - code := srv.CreateAuthorizationCodeFor(user) - nonce := pls.GenerateRandomHex(32) - - r := test.Request( - "GET", - "/session/callback?code="+code+"&state=invalid", - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - tokens, err := svc.Exchange(r) - - require.Error(t, err) - assert.Nil(t, tokens) - }) - - t.Run("with an invalid authorization code grant", func(t *testing.T) { - nonce := pls.GenerateRandomHex(32) - - r := test.Request( - "GET", "/session/callback?code=invalid", - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - - tokens, err := svc.Exchange(r) - - require.Error(t, err) - assert.Nil(t, tokens) - }) - - t.Run("with a valid grant", func(t *testing.T) { - user := mockoidc.DefaultUser() - code := srv.CreateAuthorizationCodeFor(user) - nonce := pls.GenerateRandomHex(32) - - r := test.Request( - "GET", - "/session/callback?code="+code+"&state="+nonce, - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - - tokens, err := svc.Exchange(r) - - require.NoError(t, err) - assert.NotNil(t, tokens) - assert.NotEmpty(t, tokens.AccessToken) - assert.NotEmpty(t, tokens.Expiry) - assert.NotEmpty(t, tokens.TokenType) - assert.NotEmpty(t, tokens.RefreshToken) - assert.NotEmpty(t, tokens.IDToken) - }) - }) -} -- cgit v1.2.3