blob: d7b256b1220f32e102e50ef8deed9e683d7af946 [file] [log] [blame]
package state_test
import (
"fmt"
"testing"
"veyron/services/store/memstore/state"
"veyron2/security"
"veyron2/storage"
)
// Simple DAG based on teams and players.
func TestPathMatchDAG(t *testing.T) {
st := state.New(rootPublicID)
sn := st.MutableSnapshot()
johnID := mkdir(t, sn, rootPublicID, "/teamsapp/players/john")
janeID := mkdir(t, sn, rootPublicID, "/teamsapp/players/jane")
joanID := mkdir(t, sn, rootPublicID, "/teamsapp/players/joan")
link(t, sn, rootPublicID, "/teamsapp/teams/rockets/players/john", johnID)
link(t, sn, rootPublicID, "/teamsapp/teams/rockets/players/jane", janeID)
link(t, sn, rootPublicID, "/teamsapp/teams/hornets/players/jane", janeID)
link(t, sn, rootPublicID, "/teamsapp/teams/hornets/players/joan", joanID)
rJohn, _ := state.CompilePathRegex(".../john")
if !sn.PathMatch(rootPublicID, johnID, rJohn) {
t.Errorf("Expected match")
}
rTRockets, _ := state.CompilePathRegex(".../teams/rockets/...")
if !sn.PathMatch(rootPublicID, johnID, rTRockets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTRockets) {
t.Errorf("Expected match")
}
if sn.PathMatch(rootPublicID, joanID, rTRockets) {
t.Errorf("Unexpected match")
}
rTHornets, _ := state.CompilePathRegex(".../teams/hornets/...")
if sn.PathMatch(rootPublicID, johnID, rTHornets) {
t.Errorf("Unexpected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTHornets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTHornets) {
t.Errorf("Expected match")
}
rTRocketsOrHornets, _ := state.CompilePathRegex(".../teams/{rockets,hornets}/...")
if !sn.PathMatch(rootPublicID, johnID, rTRocketsOrHornets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTRocketsOrHornets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTRocketsOrHornets) {
t.Errorf("Expected match")
}
rTJoanOrRockets, _ := state.CompilePathRegex(".../{players/joan,teams/rockets}/...")
if !sn.PathMatch(rootPublicID, johnID, rTJoanOrRockets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTJoanOrRockets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTJoanOrRockets) {
t.Errorf("Expected match")
}
}
// Similar to above, but introduce loops by adding a teams directory to each of
// the players, looping back to their teams.
func TestPathMatchLoop(t *testing.T) {
st := state.New(rootPublicID)
sn := st.MutableSnapshot()
johnID := mkdir(t, sn, rootPublicID, "/teamsapp/players/john")
janeID := mkdir(t, sn, rootPublicID, "/teamsapp/players/jane")
joanID := mkdir(t, sn, rootPublicID, "/teamsapp/players/joan")
rocketsID := mkdir(t, sn, rootPublicID, "/teamsapp/teams/rockets")
hornetsID := mkdir(t, sn, rootPublicID, "/teamsapp/teams/hornets")
link(t, sn, rootPublicID, "/teamsapp/teams/rockets/players/john", johnID)
link(t, sn, rootPublicID, "/teamsapp/teams/rockets/players/jane", janeID)
link(t, sn, rootPublicID, "/teamsapp/teams/hornets/players/jane", janeID)
link(t, sn, rootPublicID, "/teamsapp/teams/hornets/players/joan", joanID)
link(t, sn, rootPublicID, "/teamsapp/players/john/teams/rockets", rocketsID)
link(t, sn, rootPublicID, "/teamsapp/players/jane/teams/rockets", rocketsID)
link(t, sn, rootPublicID, "/teamsapp/players/jane/teams/hornets", hornetsID)
link(t, sn, rootPublicID, "/teamsapp/players/joan/teams/hornets", hornetsID)
if err := st.ApplyMutations(sn.Mutations()); err != nil {
t.Errorf("ApplyMutations failed: %s", err)
}
rJohn, _ := state.CompilePathRegex(".../john")
if !sn.PathMatch(rootPublicID, johnID, rJohn) {
t.Errorf("Expected match")
}
rTRockets, _ := state.CompilePathRegex(".../teams/rockets/players/*")
if !sn.PathMatch(rootPublicID, johnID, rTRockets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTRockets) {
t.Errorf("Expected match")
}
if sn.PathMatch(rootPublicID, joanID, rTRockets) {
t.Errorf("Unexpected match")
}
rTHornets, _ := state.CompilePathRegex(".../teams/hornets/players/*")
if sn.PathMatch(rootPublicID, johnID, rTHornets) {
t.Errorf("Unexpected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTHornets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTHornets) {
t.Errorf("Expected match")
}
rTFancyPath, _ := state.CompilePathRegex(".../teams/rockets/players/*/teams/hornets/players/*")
if sn.PathMatch(rootPublicID, johnID, rTFancyPath) {
t.Errorf("Unexpected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTFancyPath) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTFancyPath) {
t.Errorf("Expected match")
}
}
// Similar to above, but use the explicit E field rather than the implicit
// directory.
func TestPathMatchFieldDAG(t *testing.T) {
st := state.New(rootPublicID)
sn := st.MutableSnapshot()
johnID := mkdir(t, sn, rootPublicID, "/E/teamsapp/E/players/E/john")
janeID := mkdir(t, sn, rootPublicID, "/E/teamsapp/E/players/E/jane")
joanID := mkdir(t, sn, rootPublicID, "/E/teamsapp/E/players/E/joan")
link(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/rockets/E/players/E/john", johnID)
link(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/rockets/E/players/E/jane", janeID)
link(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/hornets/E/players/E/jane", janeID)
link(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/hornets/E/players/E/joan", joanID)
if err := st.ApplyMutations(sn.Mutations()); err != nil {
t.Errorf("ApplyMutations failed: %s", err)
}
rJohn, _ := state.CompilePathRegex(".../E/john")
if !sn.PathMatch(rootPublicID, johnID, rJohn) {
t.Errorf("Expected match")
}
rTRockets, _ := state.CompilePathRegex(".../E/teams/E/rockets/E/...")
if !sn.PathMatch(rootPublicID, johnID, rTRockets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTRockets) {
t.Errorf("Expected match")
}
if sn.PathMatch(rootPublicID, joanID, rTRockets) {
t.Errorf("Unexpected match")
}
rTHornets, _ := state.CompilePathRegex(".../E/teams/E/hornets/E/...")
if sn.PathMatch(rootPublicID, johnID, rTHornets) {
t.Errorf("Unexpected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTHornets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTHornets) {
t.Errorf("Expected match")
}
rTRocketsOrHornets, _ := state.CompilePathRegex(".../E/teams/E/{rockets,hornets}/E/...")
if !sn.PathMatch(rootPublicID, johnID, rTRocketsOrHornets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTRocketsOrHornets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTRocketsOrHornets) {
t.Errorf("Expected match")
}
rTJoanOrRockets, _ := state.CompilePathRegex(".../E/{players/E/joan,teams/E/rockets}/...")
if !sn.PathMatch(rootPublicID, johnID, rTJoanOrRockets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTJoanOrRockets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTJoanOrRockets) {
t.Errorf("Expected match")
}
}
func TestPathMatchFieldLoop(t *testing.T) {
st := state.New(rootPublicID)
sn := st.MutableSnapshot()
johnID := mkdir(t, sn, rootPublicID, "/E/teamsapp/E/players/E/john")
janeID := mkdir(t, sn, rootPublicID, "/E/teamsapp/E/players/E/jane")
joanID := mkdir(t, sn, rootPublicID, "/E/teamsapp/E/players/E/joan")
rocketsID := mkdir(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/rockets")
hornetsID := mkdir(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/hornets")
link(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/rockets/E/players/E/john", johnID)
link(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/rockets/E/players/E/jane", janeID)
link(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/hornets/E/players/E/jane", janeID)
link(t, sn, rootPublicID, "/E/teamsapp/E/teams/E/hornets/E/players/E/joan", joanID)
link(t, sn, rootPublicID, "/E/teamsapp/E/players/E/john/E/teams/E/rockets", rocketsID)
link(t, sn, rootPublicID, "/E/teamsapp/E/players/E/jane/E/teams/E/rockets", rocketsID)
link(t, sn, rootPublicID, "/E/teamsapp/E/players/E/jane/E/teams/E/hornets", hornetsID)
link(t, sn, rootPublicID, "/E/teamsapp/E/players/E/joan/E/teams/E/hornets", hornetsID)
if err := st.ApplyMutations(sn.Mutations()); err != nil {
t.Errorf("ApplyMutations failed: %s", err)
}
rJohn, _ := state.CompilePathRegex(".../E/john")
if !sn.PathMatch(rootPublicID, johnID, rJohn) {
t.Errorf("Expected match")
}
rTRockets, _ := state.CompilePathRegex(".../E/teams/E/rockets/E/players/E/*")
if !sn.PathMatch(rootPublicID, johnID, rTRockets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTRockets) {
t.Errorf("Expected match")
}
if sn.PathMatch(rootPublicID, joanID, rTRockets) {
t.Errorf("Unexpected match")
}
rTHornets, _ := state.CompilePathRegex(".../E/teams/E/hornets/E/players/E/*")
if sn.PathMatch(rootPublicID, johnID, rTHornets) {
t.Errorf("Unexpected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTHornets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTHornets) {
t.Errorf("Expected match")
}
rTFancyPath, _ := state.CompilePathRegex(".../E/teams/E/rockets/E/players/E/*/E/teams/E/hornets/E/players/E/*")
if sn.PathMatch(rootPublicID, johnID, rTFancyPath) {
t.Errorf("Unexpected match")
}
if !sn.PathMatch(rootPublicID, janeID, rTFancyPath) {
t.Errorf("Expected match")
}
if !sn.PathMatch(rootPublicID, joanID, rTFancyPath) {
t.Errorf("Expected match")
}
}
// Create a player, and add security restrictions.
func mkSecureHome(t *testing.T, sn *state.MutableSnapshot, pid security.PublicID, name string, user security.PrincipalPattern) (storage.ID, storage.TagList) {
id := mkdir(t, sn, rootPublicID, fmt.Sprintf("/teamsapp/home/%s", name))
aclID := putPath(t, sn, rootPublicID, fmt.Sprintf("/teamsapp/home/%s/acls/rwa", name), &storage.ACL{
Name: name,
Contents: security.ACL{
user: security.LabelSet(security.ReadLabel | security.WriteLabel | security.AdminLabel),
},
})
tags := storage.TagList{
storage.Tag{Op: storage.RemoveACL, ACL: state.EveryoneACLID},
storage.Tag{Op: storage.AddInheritedACL, ACL: aclID},
}
put(t, sn, rootPublicID, fmt.Sprintf("/teamsapp/home/%s/.tags", name), tags)
return id, tags
}
// Check security restrictions.
func TestPathMatchSecurity(t *testing.T) {
st := state.New(rootPublicID)
sn := st.MutableSnapshot()
// Create the players and set their ACLs.
johnID, johnTags := mkSecureHome(t, sn, rootPublicID, "john", johnUser)
janeID, janeTags := mkSecureHome(t, sn, rootPublicID, "jane", janeUser)
joanID, _ := mkSecureHome(t, sn, rootPublicID, "joan", joanUser)
rocketsID := mkdir(t, sn, rootPublicID, "/teamsapp/teams/rockets")
hornetsID := mkdir(t, sn, rootPublicID, "/teamsapp/teams/hornets")
link(t, sn, rootPublicID, "/teamsapp/teams/rockets/players/john", johnID)
link(t, sn, rootPublicID, "/teamsapp/teams/rockets/players/jane", janeID)
link(t, sn, rootPublicID, "/teamsapp/teams/hornets/players/jane", janeID)
link(t, sn, rootPublicID, "/teamsapp/teams/hornets/players/joan", joanID)
link(t, sn, rootPublicID, "/teamsapp/home/john/teams/rockets", rocketsID)
link(t, sn, rootPublicID, "/teamsapp/home/jane/teams/rockets", rocketsID)
link(t, sn, rootPublicID, "/teamsapp/home/jane/teams/hornets", hornetsID)
link(t, sn, rootPublicID, "/teamsapp/home/joan/teams/hornets", hornetsID)
if err := st.ApplyMutations(sn.Mutations()); err != nil {
t.Errorf("ApplyMutations failed: %s", err)
}
sn = st.MutableSnapshot()
rJohn, _ := state.CompilePathRegex(".../john")
if !sn.PathMatch(rootPublicID, johnID, rJohn) {
t.Errorf("Expected match")
}
if !sn.PathMatch(johnPublicID, johnID, rJohn) {
t.Errorf("Expected match")
}
if !sn.PathMatch(janePublicID, johnID, rJohn) {
t.Errorf("Expected match")
}
rTRockets, _ := state.CompilePathRegex(".../teams/rockets/players/*")
if !sn.PathMatch(janePublicID, johnID, rTRockets) {
t.Errorf("Expected match")
}
if !sn.PathMatch(janePublicID, janeID, rTRockets) {
t.Errorf("Expected match")
}
if sn.PathMatch(janePublicID, joanID, rTRockets) {
t.Errorf("Unexpected match")
}
rHomeJane, _ := state.CompilePathRegex(".../home/jane")
if _, err := sn.Get(johnPublicID, storage.ParsePath("/teamsapp/home/jane")); err == nil {
t.Errorf("Security error")
}
// John can't see Jane's home path.
if sn.PathMatch(johnPublicID, janeID, rHomeJane) {
t.Errorf("Unexpected match")
}
// Jane can see it.
if !sn.PathMatch(janePublicID, janeID, rHomeJane) {
t.Errorf("Expected match")
}
// Both can see Jane through the teams directory.
rPlayersJane, _ := state.CompilePathRegex(".../players/jane")
if !sn.PathMatch(johnPublicID, janeID, rPlayersJane) {
t.Errorf("Expected match")
}
if !sn.PathMatch(janePublicID, janeID, rPlayersJane) {
t.Errorf("Expected match")
}
// Restrict /teamsapp/teams to Jane.
put(t, sn, rootPublicID, "/teamsapp/teams/.tags", janeTags)
// John can still see through /teamsapp/home/john/teams/rockets/players/jane.
if !sn.PathMatch(johnPublicID, janeID, rPlayersJane) {
t.Errorf("Expected match")
}
if !sn.PathMatch(janePublicID, janeID, rPlayersJane) {
t.Errorf("Expected match")
}
// Restrict /teamsapp/teams/rockets.
put(t, sn, rootPublicID, "/teamsapp/teams/rockets/.tags", janeTags)
// We took away EveryoneACLID, but John is still inherited.
if !sn.PathMatch(johnPublicID, janeID, rPlayersJane) {
t.Errorf("Expected match")
}
if !sn.PathMatch(janePublicID, janeID, rPlayersJane) {
t.Errorf("Expected match")
}
// Take away John from the rockets too.
tag := storage.Tag{Op: storage.RemoveACL, ACL: johnTags[1].ACL}
put(t, sn, rootPublicID, "/teamsapp/teams/rockets/.tags/@", tag)
// John is now locked out.
if sn.PathMatch(johnPublicID, janeID, rPlayersJane) {
t.Errorf("Unexpected match")
}
if !sn.PathMatch(janePublicID, janeID, rPlayersJane) {
t.Errorf("Expected match")
}
}