Files
warmbly/internal/api/handler/pool_link_consent_test.go
T

105 lines
4.0 KiB
Go

package handler
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/warmbly/warmbly/internal/app/poollink"
"github.com/warmbly/warmbly/internal/errx"
"github.com/warmbly/warmbly/internal/models"
)
type fakeBroker struct {
poollink.Service
bound, completedWith string
}
func (f *fakeBroker) DescribeOAuthConsent(context.Context, string) (*models.PoolLinkOAuthConsent, *errx.Error) {
return &models.PoolLinkOAuthConsent{Provider: models.InboxProviderGoogle, InstanceHost: "warmbly.acme.io", WorkspaceName: "Acme"}, nil
}
func (f *fakeBroker) ContinueOAuth(_ context.Context, _ string, binding string) (string, *errx.Error) {
f.bound = binding
return "https://accounts.google.com/o/oauth2/auth?x=1", nil
}
func (f *fakeBroker) CompleteOAuthCallback(_ context.Context, _, _, _, _, binding string) (string, *errx.Error) {
f.completedWith = binding
if binding == "" {
return "", poollink.ErrOAuthBrowser
}
return "https://warmbly.acme.io/cloud-oauth/done?status=ok", nil
}
func serve(t *testing.T, fn gin.HandlerFunc, target string, cookies ...*http.Cookie) *httptest.ResponseRecorder {
t.Helper()
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodGet, target, nil)
for _, ck := range cookies {
c.Request.AddCookie(ck)
}
fn(c)
return w
}
func TestBrokeredSigninCompletesOnlyInTheBrowserThatContinued(t *testing.T) {
f := &fakeBroker{}
h := &Handler{PoolLinkService: f}
page := serve(t, h.PoolLinkOAuthConsentPage, "/addresses/connect?state=pl_x")
body := page.Body.String()
if page.Code != http.StatusOK || !strings.Contains(body, "warmbly.acme.io") || !strings.Contains(body, "Acme") || strings.Contains(body, "accounts.google.com") {
t.Fatalf("consent page = %d %s", page.Code, body)
}
cookies := page.Result().Cookies()
if len(cookies) != 1 || cookies[0].Name != brokerCookieName || !cookies[0].HttpOnly || cookies[0].SameSite != http.SameSiteLaxMode {
t.Fatalf("cookie = %+v", cookies)
}
token := cookies[0].Value
if w := serve(t, h.PoolLinkOAuthContinue, "/addresses/connect/continue?state=pl_x&t="+token); w.Code == http.StatusFound || f.bound != "" {
t.Fatalf("continue without the cookie = %d", w.Code)
}
if w := serve(t, h.PoolLinkOAuthContinue, "/addresses/connect/continue?state=pl_x&t=wrong", cookies[0]); w.Code == http.StatusFound || f.bound != "" {
t.Fatalf("continue with a token the page did not render = %d", w.Code)
}
if w := serve(t, h.PoolLinkOAuthContinue, "/addresses/connect/continue?state=pl_x&t="+token, cookies[0]); w.Code != http.StatusFound || f.bound != token {
t.Fatalf("continue from the page = %d, bound %q", w.Code, f.bound)
}
if w := serve(t, h.EmailOAuthCallbackGmail, "/addresses/google/callback?state=pl_x&code=c"); w.Code == http.StatusFound || strings.Contains(w.Body.String(), "postMessage") {
t.Fatalf("callback in another browser = %d %s", w.Code, w.Body.String())
}
if w := serve(t, h.EmailOAuthCallbackGmail, "/addresses/google/callback?state=pl_x&code=c", cookies[0]); w.Code != http.StatusFound || f.completedWith != token {
t.Fatalf("callback in the bound browser = %d", w.Code)
}
}
func TestCallbackPostsOnlyToAConfiguredOrigin(t *testing.T) {
t.Setenv("APP_ORIGIN", "")
t.Setenv("APP_URL", "")
t.Setenv("FRONTEND_BASE_URL", "")
t.Setenv("CORS_ALLOW_ORIGINS", "")
t.Setenv("PUBLIC_HOST", "")
t.Setenv("DEPLOYMENT_MODE", "self_hosted")
h := &Handler{}
for _, fn := range []gin.HandlerFunc{h.EmailOAuthCallbackGmail, h.IntegrationOAuthCallback} {
body := serve(t, fn, "/cb?state=s&code=c").Body.String()
if strings.Contains(body, `"*"`) || !strings.Contains(body, "APP_URL") {
t.Fatalf("unconfigured origin page = %s", body)
}
}
t.Setenv("APP_ORIGIN", "https://app.acme.io")
body := serve(t, h.EmailOAuthCallbackGmail, "/cb?state=s&code=c").Body.String()
if !strings.Contains(body, `"https://app.acme.io"`) || strings.Contains(body, `"*"`) {
t.Fatalf("configured origin page = %s", body)
}
}