Files
Excalidraw/workspace/sharing_test.go
T

221 lines
7.6 KiB
Go

package workspace
import (
"encoding/json"
"net/http"
"testing"
)
func TestInviteAcceptAddsEditorMembership(t *testing.T) {
api, cleanup := newTestAPI(t)
defer cleanup()
aliceCookie, _, aliceTeam := signup(t, api, "alice@example.com")
bobCookie, _, _ := signup(t, api, "bob@example.com")
inviteRR := doJSON(t, api, http.MethodPost, "/teams/"+aliceTeam.ID+"/invites", map[string]any{
"email": "bob@example.com",
"role": "editor",
}, aliceCookie)
if inviteRR.Code != http.StatusCreated {
t.Fatalf("invite status = %d body = %s", inviteRR.Code, inviteRR.Body.String())
}
var invite struct {
Invite TeamInvite `json:"invite"`
Token string `json:"token"`
}
if err := json.Unmarshal(inviteRR.Body.Bytes(), &invite); err != nil {
t.Fatalf("invite decode error = %v", err)
}
if invite.Token == "" || invite.Invite.Email != "bob@example.com" {
t.Fatalf("unexpected invite: %#v", invite)
}
beforeRR := doJSON(t, api, http.MethodGet, "/teams/"+aliceTeam.ID+"/members", nil, bobCookie)
if beforeRR.Code != http.StatusForbidden {
t.Fatalf("bob members before accept status = %d body = %s", beforeRR.Code, beforeRR.Body.String())
}
acceptRR := doJSON(t, api, http.MethodPost, "/invites/accept", map[string]string{"token": invite.Token}, bobCookie)
if acceptRR.Code != http.StatusOK {
t.Fatalf("accept status = %d body = %s", acceptRR.Code, acceptRR.Body.String())
}
afterRR := doJSON(t, api, http.MethodGet, "/teams/"+aliceTeam.ID+"/members", nil, bobCookie)
if afterRR.Code != http.StatusOK {
t.Fatalf("bob members after accept status = %d body = %s", afterRR.Code, afterRR.Body.String())
}
}
func TestRestrictedDrawingGrantAllowsViewNotEdit(t *testing.T) {
api, cleanup := newTestAPI(t)
defer cleanup()
aliceCookie, _, aliceTeam := signup(t, api, "alice@example.com")
bobCookie, _, _ := signup(t, api, "bob@example.com")
createRR := doJSON(t, api, http.MethodPost, "/drawings", map[string]any{
"team_id": aliceTeam.ID,
"title": "Restricted map",
"visibility": "restricted",
}, aliceCookie)
if createRR.Code != http.StatusCreated {
t.Fatalf("create drawing status = %d body = %s", createRR.Code, createRR.Body.String())
}
var drawing Drawing
if err := json.Unmarshal(createRR.Body.Bytes(), &drawing); err != nil {
t.Fatalf("drawing decode error = %v", err)
}
noAccessRR := doJSON(t, api, http.MethodGet, "/drawings/"+drawing.ID, nil, bobCookie)
if noAccessRR.Code != http.StatusForbidden {
t.Fatalf("bob get before grant status = %d body = %s", noAccessRR.Code, noAccessRR.Body.String())
}
grantRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/permissions", map[string]any{
"subject_type": "user",
"email": "bob@example.com",
"permission": "view",
}, aliceCookie)
if grantRR.Code != http.StatusCreated {
t.Fatalf("grant status = %d body = %s", grantRR.Code, grantRR.Body.String())
}
getRR := doJSON(t, api, http.MethodGet, "/drawings/"+drawing.ID, nil, bobCookie)
if getRR.Code != http.StatusOK {
t.Fatalf("bob get after grant status = %d body = %s", getRR.Code, getRR.Body.String())
}
editRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/revisions", map[string]any{
"snapshot": map[string]any{"type": "excalidraw", "elements": []any{}},
}, bobCookie)
if editRR.Code != http.StatusForbidden {
t.Fatalf("bob edit with view grant status = %d body = %s", editRR.Code, editRR.Body.String())
}
}
func TestShareLinkAllowsUnauthenticatedDrawingRead(t *testing.T) {
api, cleanup := newTestAPI(t)
defer cleanup()
aliceCookie, _, aliceTeam := signup(t, api, "alice@example.com")
createRR := doJSON(t, api, http.MethodPost, "/drawings", map[string]any{
"team_id": aliceTeam.ID,
"title": "Shared map",
}, aliceCookie)
if createRR.Code != http.StatusCreated {
t.Fatalf("create drawing status = %d body = %s", createRR.Code, createRR.Body.String())
}
var drawing Drawing
if err := json.Unmarshal(createRR.Body.Bytes(), &drawing); err != nil {
t.Fatalf("drawing decode error = %v", err)
}
shareRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/share-links", map[string]any{
"permission": "view",
}, aliceCookie)
if shareRR.Code != http.StatusCreated {
t.Fatalf("share status = %d body = %s", shareRR.Code, shareRR.Body.String())
}
var share struct {
ShareLink ShareLink `json:"share_link"`
Token string `json:"token"`
}
if err := json.Unmarshal(shareRR.Body.Bytes(), &share); err != nil {
t.Fatalf("share decode error = %v", err)
}
if share.Token == "" || share.ShareLink.TokenHash != "" {
t.Fatalf("unexpected share response: %#v", share)
}
publicRR := doJSON(t, api, http.MethodGet, "/shared/"+share.Token, nil)
if publicRR.Code != http.StatusOK {
t.Fatalf("public shared status = %d body = %s", publicRR.Code, publicRR.Body.String())
}
}
func TestEmbedsRejectUnsafeURLs(t *testing.T) {
api, cleanup := newTestAPI(t)
defer cleanup()
cookie, _, team := signup(t, api, "alice@example.com")
createRR := doJSON(t, api, http.MethodPost, "/drawings", map[string]any{
"team_id": team.ID,
"title": "Embed map",
}, cookie)
if createRR.Code != http.StatusCreated {
t.Fatalf("create drawing status = %d body = %s", createRR.Code, createRR.Body.String())
}
var drawing Drawing
if err := json.Unmarshal(createRR.Body.Bytes(), &drawing); err != nil {
t.Fatalf("drawing decode error = %v", err)
}
for _, unsafeURL := range []string{"javascript:alert(1)", "http://127.0.0.1/admin", "http://localhost:3002"} {
rr := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/embeds", map[string]any{
"source_url": unsafeURL,
"embed_type": "link",
}, cookie)
if rr.Code != http.StatusBadRequest {
t.Fatalf("unsafe url %q status = %d body = %s", unsafeURL, rr.Code, rr.Body.String())
}
}
safeRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/embeds", map[string]any{
"source_url": "https://example.com/roadmap",
"embed_type": "link",
}, cookie)
if safeRR.Code != http.StatusCreated {
t.Fatalf("safe embed status = %d body = %s", safeRR.Code, safeRR.Body.String())
}
}
func TestAssetsAndLinkReferences(t *testing.T) {
api, cleanup := newTestAPI(t)
defer cleanup()
cookie, _, team := signup(t, api, "alice@example.com")
projectRR := doJSON(t, api, http.MethodPost, "/projects", map[string]any{
"team_id": team.ID,
"name": "Roadmap",
}, cookie)
if projectRR.Code != http.StatusCreated {
t.Fatalf("project status = %d body = %s", projectRR.Code, projectRR.Body.String())
}
var project Project
if err := json.Unmarshal(projectRR.Body.Bytes(), &project); err != nil {
t.Fatalf("project decode error = %v", err)
}
drawingRR := doJSON(t, api, http.MethodPost, "/drawings", map[string]any{
"team_id": team.ID,
"title": "Linked map",
}, cookie)
if drawingRR.Code != http.StatusCreated {
t.Fatalf("drawing status = %d body = %s", drawingRR.Code, drawingRR.Body.String())
}
var drawing Drawing
if err := json.Unmarshal(drawingRR.Body.Bytes(), &drawing); err != nil {
t.Fatalf("drawing decode error = %v", err)
}
assetRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/assets", map[string]any{
"kind": "attachment",
"mime_type": "image/png",
"size": 2048,
"width": 800,
"height": 600,
}, cookie)
if assetRR.Code != http.StatusCreated {
t.Fatalf("asset status = %d body = %s", assetRR.Code, assetRR.Body.String())
}
linkRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/links", map[string]any{
"target_resource_type": "project",
"target_resource_id": project.ID,
"label": "Roadmap project",
}, cookie)
if linkRR.Code != http.StatusCreated {
t.Fatalf("link status = %d body = %s", linkRR.Code, linkRR.Body.String())
}
}