84 lines
2.8 KiB
Go
84 lines
2.8 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/kandev/kandev/internal/auth/authn"
|
|
"github.com/kandev/kandev/internal/common/logger"
|
|
"github.com/kandev/kandev/internal/task/repository/repoerrors"
|
|
ws "github.com/kandev/kandev/pkg/websocket"
|
|
)
|
|
|
|
func TestReviewWatchWebSocketDeniesForeignWorkspaceListAndMutations(t *testing.T) {
|
|
store := newTestStore(t)
|
|
watch := &ReviewWatch{WorkspaceID: "workspace-b", Prompt: "workspace-b review secret"}
|
|
if err := store.CreateReviewWatch(context.Background(), watch); err != nil {
|
|
t.Fatalf("create victim review watch: %v", err)
|
|
}
|
|
|
|
svc := NewService(nil, AuthMethodNone, nil, store, nil, testLogger(t))
|
|
svc.SetWorkspaceAuthorizer(func(ctx context.Context, workspaceID string) error {
|
|
identity, ok := authn.IdentityFromContext(ctx)
|
|
if !ok || identity.UserID != "member-a" || identity.Role != authn.RoleMember {
|
|
t.Fatalf("authorizer identity = %#v, %v; want authenticated member-a", identity, ok)
|
|
}
|
|
if workspaceID != watch.WorkspaceID {
|
|
t.Fatalf("authorizer workspace = %q, want %q", workspaceID, watch.WorkspaceID)
|
|
}
|
|
return repoerrors.ErrWorkspaceNotFound
|
|
})
|
|
|
|
dispatcher := ws.NewDispatcher()
|
|
registerWSHandlers(dispatcher, svc, logger.Default())
|
|
ctx := authn.WithIdentity(context.Background(), authn.Identity{UserID: "member-a", Role: authn.RoleMember})
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
action string
|
|
payload map[string]any
|
|
}{
|
|
{
|
|
name: "list",
|
|
action: ws.ActionGitHubReviewWatchesList,
|
|
payload: map[string]any{"workspace_id": watch.WorkspaceID},
|
|
},
|
|
{
|
|
name: "update",
|
|
action: ws.ActionGitHubReviewWatchUpdate,
|
|
payload: map[string]any{"id": watch.ID, "prompt": "attacker update"},
|
|
},
|
|
{
|
|
name: "delete",
|
|
action: ws.ActionGitHubReviewWatchDelete,
|
|
payload: map[string]any{"id": watch.ID},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
request, err := ws.NewRequest("request-"+tc.name, tc.action, tc.payload)
|
|
if err != nil {
|
|
t.Fatalf("create request: %v", err)
|
|
}
|
|
response, err := dispatcher.Dispatch(ctx, request)
|
|
if err != nil {
|
|
t.Fatalf("dispatch: %v", err)
|
|
}
|
|
if response.Type != ws.MessageTypeError || !strings.Contains(string(response.Payload), ws.ErrorCodeNotFound) {
|
|
t.Fatalf("response = %#v, want sanitized NOT_FOUND", response)
|
|
}
|
|
if strings.Contains(string(response.Payload), watch.ID) || strings.Contains(string(response.Payload), watch.Prompt) {
|
|
t.Fatalf("response = %s, must not disclose victim watch metadata", response.Payload)
|
|
}
|
|
})
|
|
}
|
|
|
|
remaining, err := store.GetReviewWatch(context.Background(), watch.ID)
|
|
if err != nil {
|
|
t.Fatalf("load victim review watch after denied mutations: %v", err)
|
|
}
|
|
if remaining == nil || remaining.Prompt != watch.Prompt {
|
|
t.Fatalf("victim review watch = %#v, want unchanged prompt %q", remaining, watch.Prompt)
|
|
}
|
|
}
|