Files
ax/src/e2e_permissions_test.go

178 lines
5.7 KiB
Go
Raw Normal View History

package main
import (
"testing"
)
func TestPermissions(t *testing.T) {
alice := newTestEnv(t, "alice")
bob := alice.withUser("bob")
// Alice creates a node (gets has_ownership automatically).
aliceNode := alice.parseNode(alice.mustAx("add", "Alice's secret", "--json"))
aliceNodeID := aliceNode.ID
// Resolve alice's user node ID.
var aliceUserID string
for _, u := range alice.parseNodes(alice.mustAx("list", "--type", "user", "--json")) {
if u.Title == "alice" {
aliceUserID = u.ID
}
}
if aliceUserID == "" {
t.Fatal("could not resolve alice's user node ID")
}
t.Run("NoAccess_CannotShow", func(t *testing.T) {
_, err := bob.ax("show", aliceNodeID)
if err == nil {
t.Error("bob should not be able to show alice's node without access")
}
})
t.Run("NoAccess_NotInList", func(t *testing.T) {
bob.mustAx("add", "Bob's scratch", "--json")
nodes := bob.parseNodes(bob.mustAx("list", "--json"))
if _, ok := bob.findInList(nodes, aliceNodeID); ok {
t.Error("alice's node should not appear in bob's list without access")
}
})
t.Run("NoAccess_CannotUpdate", func(t *testing.T) {
_, err := bob.ax("update", aliceNodeID, "--title", "hacked")
if err == nil {
t.Error("bob should not be able to update alice's node without access")
}
})
// Resolve bob's user node ID. User nodes are globally readable.
var bobUserID string
for _, u := range alice.parseNodes(alice.mustAx("list", "--type", "user", "--json")) {
if u.Title == "bob" {
bobUserID = u.ID
}
}
if bobUserID == "" {
t.Fatal("could not resolve bob's user node ID")
}
t.Run("SelfEscalation_Denied", func(t *testing.T) {
_, err := bob.ax("update", bobUserID, "--rel", "can_write:"+aliceNodeID)
if err == nil {
t.Error("bob should not be able to grant himself write access to alice's node")
}
})
t.Run("ReadAccess_Grant", func(t *testing.T) {
alice.mustAx("update", bobUserID, "--rel", "can_read:"+aliceNodeID)
if _, err := bob.ax("show", aliceNodeID); err != nil {
t.Error("bob should be able to show alice's node after read access granted")
}
nodes := bob.parseNodes(bob.mustAx("list", "--json"))
if _, ok := bob.findInList(nodes, aliceNodeID); !ok {
t.Error("alice's node should appear in bob's list after read access granted")
}
})
t.Run("ReadAccess_CannotUpdate", func(t *testing.T) {
_, err := bob.ax("update", aliceNodeID, "--title", "hacked with read access")
if err == nil {
t.Error("bob should not be able to update alice's node with only read access")
}
})
t.Run("ReadAccess_CannotAddRelationToNode", func(t *testing.T) {
bobLinkedID := bob.parseNode(bob.mustAx("add", "Bob's linked node", "--json")).ID
_, err := bob.ax("update", bobLinkedID, "--rel", "related:"+aliceNodeID)
if err == nil {
t.Error("bob should not be able to create a relation to alice's node with only read access")
}
})
t.Run("WriteAccess_Grant", func(t *testing.T) {
alice.mustAx("update", bobUserID, "--rel", "can_write:"+aliceNodeID)
out := bob.mustAx("update", aliceNodeID, "--title", "Bob modified this", "--json")
n := bob.parseNode(out)
if n.Title != "Bob modified this" {
t.Errorf("expected 'Bob modified this', got %q", n.Title)
}
})
t.Run("WriteAccess_CanAddRelationToNode", func(t *testing.T) {
bobNode2 := bob.parseNode(bob.mustAx("add", "Bob's related node", "--json"))
bob.mustAx("update", bobNode2.ID, "--rel", "related:"+aliceNodeID)
out := bob.mustAx("show", bobNode2.ID, "--json")
n := bob.parseNode(out)
if !n.HasRelation("related", aliceNodeID) {
t.Error("expected related relation after write access granted")
}
})
t.Run("Ownership_DefaultOnCreate", func(t *testing.T) {
userOut := alice.mustAx("show", aliceUserID, "--json")
userNode := alice.parseNode(userOut)
if !userNode.HasRelation("has_ownership", aliceUserID) {
t.Errorf("expected user node to have self-ownership, got relations: %v", userNode.Relations)
}
if !userNode.HasRelation("has_ownership", aliceNodeID) {
t.Errorf("expected alice to own her node, got relations: %v", userNode.Relations)
}
})
t.Run("Ownership_CascadeDelete", func(t *testing.T) {
throwaway := alice.withUser("throwaway")
child := throwaway.parseNode(throwaway.mustAx("add", "Child node", "--json"))
var throwawayUserID string
for _, u := range throwaway.parseNodes(throwaway.mustAx("list", "--type", "user", "--json")) {
if u.Title == "throwaway" {
throwawayUserID = u.ID
}
}
if throwawayUserID == "" {
t.Fatal("could not find throwaway user node")
}
throwaway.mustAx("del", throwawayUserID, "--force")
_, err := throwaway.ax("show", child.ID)
if err == nil {
t.Error("child node should have been cascade-deleted when its owner was deleted")
}
})
}
func TestNamespaceExplicitCreate(t *testing.T) {
env := newTestEnv(t, "testuser")
nsNode := env.parseNode(env.mustAx("add", "myworkspace", "--type", "namespace", "--json"))
if !nsNode.HasRelation("in_namespace", nsNode.ID) {
t.Errorf("expected namespace to have in_namespace pointing to itself, got relations: %v", nsNode.Relations)
}
users := env.parseNodes(env.mustAx("list", "--type", "user", "--json"))
var userNode *NodeResponse
for i := range users {
if users[i].Title == "testuser" {
userNode = &users[i]
break
}
}
if userNode == nil {
t.Fatal("could not find testuser node")
}
if !userNode.HasRelation("has_ownership", nsNode.ID) {
t.Errorf("expected creator to have has_ownership on new namespace, got relations: %v", userNode.Relations)
}
env.mustAx("add", "task in workspace", "--namespace", "myworkspace", "--json")
listed := env.parseNodes(env.mustAx("list", "--namespace", "myworkspace", "--json"))
if len(listed) == 0 {
t.Error("expected to list nodes in newly created namespace")
}
}