Files
kandev/apps/backend/internal/github/handlers_authorization_test.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)
}
}