blob: a25d886e4fed733bd9b8549a798b66fd1310b03c [file] [log] [blame]
package server_test
import (
"reflect"
"runtime/debug"
"testing"
"v.io/core/veyron2"
"v.io/core/veyron2/context"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/security"
"v.io/core/veyron2/services/security/access"
"v.io/core/veyron2/services/security/groups"
"v.io/core/veyron2/verror"
"v.io/core/veyron2/vlog"
tsecurity "v.io/core/veyron/lib/testutil/security"
_ "v.io/core/veyron/profiles"
"v.io/core/veyron/services/security/groups/memstore"
"v.io/core/veyron/services/security/groups/server"
)
func getEntriesOrDie(g groups.GroupClientStub, ctx *context.T, t *testing.T) map[groups.BlessingPatternChunk]struct{} {
res, _, err := g.Get(ctx, groups.GetRequest{}, "")
if err != nil {
debug.PrintStack()
t.Fatal("Get failed: ", err)
}
return res.Entries
}
func getACLOrDie(g groups.GroupClientStub, ctx *context.T, t *testing.T) access.TaggedACLMap {
res, _, err := g.GetACL(ctx)
if err != nil {
debug.PrintStack()
t.Fatal("GetACL failed: ", err)
}
return res
}
func getEtagOrDie(g groups.GroupClientStub, ctx *context.T, t *testing.T) string {
_, etag, err := g.Get(ctx, groups.GetRequest{}, "")
if err != nil {
debug.PrintStack()
t.Fatal("Get failed: ", err)
}
return etag
}
func bpc(chunk string) groups.BlessingPatternChunk {
return groups.BlessingPatternChunk(chunk)
}
func bpcSet(chunks ...string) map[groups.BlessingPatternChunk]struct{} {
res := map[groups.BlessingPatternChunk]struct{}{}
for _, chunk := range chunks {
res[bpc(chunk)] = struct{}{}
}
return res
}
func bpcSlice(chunks ...string) []groups.BlessingPatternChunk {
res := []groups.BlessingPatternChunk{}
for _, chunk := range chunks {
res = append(res, bpc(chunk))
}
return res
}
func entriesEqual(a, b map[groups.BlessingPatternChunk]struct{}) bool {
// Unlike DeepEqual, we treat nil and empty maps as equivalent.
if len(a) == 0 && len(b) == 0 {
return true
}
return reflect.DeepEqual(a, b)
}
func newServer(ctx *context.T) (string, func()) {
s, err := veyron2.NewServer(ctx)
if err != nil {
vlog.Fatal("veyron2.NewServer() failed: ", err)
}
eps, err := s.Listen(veyron2.GetListenSpec(ctx))
if err != nil {
vlog.Fatal("s.Listen() failed: ", err)
}
// TODO(sadovsky): Pass in an ACL and test ACL-checking in Group.Create().
acl := access.TaggedACLMap{}
m := server.NewManager(memstore.New(), acl)
if err := s.ServeDispatcher("", m); err != nil {
vlog.Fatal("s.ServeDispatcher() failed: ", err)
}
name := naming.JoinAddressName(eps[0].String(), "")
return name, func() {
s.Stop()
}
}
func setupOrDie() (clientCtx *context.T, serverName string, cleanup func()) {
ctx, shutdown := veyron2.Init()
cp, sp := tsecurity.NewPrincipal("client"), tsecurity.NewPrincipal("server")
// Have the server principal bless the client principal as "client".
blessings, err := sp.Bless(cp.PublicKey(), sp.BlessingStore().Default(), "client", security.UnconstrainedUse())
if err != nil {
vlog.Fatal("sp.Bless() failed: ", err)
}
// Have the client present its "client" blessing when talking to the server.
if _, err := cp.BlessingStore().Set(blessings, "server"); err != nil {
vlog.Fatal("cp.BlessingStore().Set() failed: ", err)
}
// Have the client treat the server's public key as an authority on all
// blessings that match the pattern "server".
if err := cp.AddToRoots(blessings); err != nil {
vlog.Fatal("cp.AddToRoots() failed: ", err)
}
clientCtx, err = veyron2.SetPrincipal(ctx, cp)
if err != nil {
vlog.Fatal("veyron2.SetPrincipal() failed: ", err)
}
serverCtx, err := veyron2.SetPrincipal(ctx, sp)
if err != nil {
vlog.Fatal("veyron2.SetPrincipal() failed: ", err)
}
serverName, stopServer := newServer(serverCtx)
cleanup = func() {
stopServer()
shutdown()
}
return
}
////////////////////////////////////////
// Test cases
// TODO(sadovsky): Currently, to be safe, the implementation always returns
// NoExistOrNoAccess, and never NoExist or NoAccess. Once the implementation is
// enhanced to differentiate between these cases, we should add corresponding
// tests.
func TestCreate(t *testing.T) {
ctx, serverName, cleanup := setupOrDie()
defer cleanup()
// Create a group with a default ACL and no entries.
g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
if err := g.Create(ctx, nil, nil); err != nil {
t.Fatal("Create failed: ", err)
}
// Verify ACL of created group.
acl := access.TaggedACLMap{}
for _, tag := range access.AllTypicalTags() {
acl.Add(security.BlessingPattern("server/client"), string(tag))
}
wantACL, gotACL := acl, getACLOrDie(g, ctx, t)
if !reflect.DeepEqual(wantACL, gotACL) {
t.Errorf("ACLs do not match: want %v, got %v", wantACL, gotACL)
}
// Verify entries of created group.
want, got := bpcSet(), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
// Creating same group again should fail, since the group already exists.
g = groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
if err := g.Create(ctx, nil, nil); !verror.Is(err, verror.ErrExist.ID) {
t.Fatal("Create should have failed")
}
// Create a group with an ACL and a few entries, including some redundant
// ones.
g = groups.GroupClient(naming.JoinAddressName(serverName, "grpB"))
acl = access.TaggedACLMap{}
// Allow Admin and Read so that we can call GetACL and Get.
for _, tag := range []access.Tag{access.Admin, access.Read} {
acl.Add(security.BlessingPattern("server/client"), string(tag))
}
if err := g.Create(ctx, acl, bpcSlice("foo", "bar", "foo")); err != nil {
t.Fatal("Create failed: ", err)
}
// Verify ACL of created group.
wantACL, gotACL = acl, getACLOrDie(g, ctx, t)
if !reflect.DeepEqual(wantACL, gotACL) {
t.Errorf("ACLs do not match: want %v, got %v", wantACL, gotACL)
}
// Verify entries of created group.
want, got = bpcSet("foo", "bar"), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
}
func TestDelete(t *testing.T) {
ctx, serverName, cleanup := setupOrDie()
defer cleanup()
// Create a group with a default ACL and no entries, check that we can delete
// it.
g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
if err := g.Create(ctx, nil, nil); err != nil {
t.Fatal("Create failed: ", err)
}
// Delete with bad etag should fail.
if err := g.Delete(ctx, "20"); !verror.Is(err, verror.ErrBadEtag.ID) {
t.Fatal("Delete should have failed with etag error")
}
// Delete with correct etag should succeed.
etag := getEtagOrDie(g, ctx, t)
if err := g.Delete(ctx, etag); err != nil {
t.Fatal("Delete failed: ", err)
}
// Check that the group was actually deleted.
if _, _, err := g.Get(ctx, groups.GetRequest{}, ""); !verror.Is(err, verror.ErrNoExistOrNoAccess.ID) {
t.Fatal("Group was not deleted")
}
// Create a group with several entries, check that we can delete it.
g = groups.GroupClient(naming.JoinAddressName(serverName, "grpB"))
if err := g.Create(ctx, nil, bpcSlice("foo", "bar", "foo")); err != nil {
t.Fatal("Create failed: ", err)
}
// Delete with empty etag should succeed.
if err := g.Delete(ctx, ""); err != nil {
t.Fatal("Delete failed: ", err)
}
// Check that the group was actually deleted.
if _, _, err := g.Get(ctx, groups.GetRequest{}, ""); !verror.Is(err, verror.ErrNoExistOrNoAccess.ID) {
t.Fatal("Group was not deleted")
}
// Check that we can recreate a group that was deleted.
if err := g.Create(ctx, nil, nil); err != nil {
t.Fatal("Create failed: ", err)
}
// Create a group with an ACL that disallows Delete(), check that Delete()
// fails.
g = groups.GroupClient(naming.JoinAddressName(serverName, "grpC"))
acl := access.TaggedACLMap{}
acl.Add(security.BlessingPattern("server/client"), string(access.Admin))
if err := g.Create(ctx, acl, nil); err != nil {
t.Fatal("Create failed: ", err)
}
// Delete should fail (no access).
if err := g.Delete(ctx, ""); !verror.Is(err, verror.ErrNoExistOrNoAccess.ID) {
t.Fatal("Delete should have failed with access error")
}
}
func TestACLMethods(t *testing.T) {
ctx, serverName, cleanup := setupOrDie()
defer cleanup()
// Create a group with a default ACL and no entries.
g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
if err := g.Create(ctx, nil, nil); err != nil {
t.Fatal("Create failed: ", err)
}
myacl := access.TaggedACLMap{}
myacl.Add(security.BlessingPattern("server/client"), string(access.Admin))
// Demonstrate that myacl differs from the default ACL.
if reflect.DeepEqual(myacl, getACLOrDie(g, ctx, t)) {
t.Fatal("ACLs should not match: %v", myacl)
}
var aclBefore, aclAfter access.TaggedACLMap
var etagBefore, etagAfter string
getACLAndEtagOrDie := func() (access.TaggedACLMap, string) {
// Doesn't use getEtagOrDie since that requires access.Read permission.
acl, etag, err := g.GetACL(ctx)
if err != nil {
debug.PrintStack()
t.Fatal("GetACL failed: ", err)
}
return acl, etag
}
// SetACL with bad etag should fail.
aclBefore, etagBefore = getACLAndEtagOrDie()
if err := g.SetACL(ctx, myacl, "20"); !verror.Is(err, verror.ErrBadEtag.ID) {
t.Fatal("SetACL should have failed with etag error")
}
// Since SetACL failed, the ACL and etag should not have changed.
aclAfter, etagAfter = getACLAndEtagOrDie()
if !reflect.DeepEqual(aclBefore, aclAfter) {
t.Errorf("ACLs do not match: want %v, got %v", aclBefore, aclAfter)
}
if etagBefore != etagAfter {
t.Errorf("Etags do not match: want %v, got %v", etagBefore, etagAfter)
}
// SetACL with correct etag should succeed.
aclBefore, etagBefore = aclAfter, etagAfter
if err := g.SetACL(ctx, myacl, etagBefore); err != nil {
t.Fatal("SetACL failed: ", err)
}
// Check that the ACL and etag actually changed.
aclAfter, etagAfter = getACLAndEtagOrDie()
if !reflect.DeepEqual(myacl, aclAfter) {
t.Errorf("ACLs do not match: want %v, got %v", myacl, aclAfter)
}
if etagBefore == etagAfter {
t.Errorf("Etags should not match: %v", etagBefore)
}
// SetACL with empty etag should succeed.
aclBefore, etagBefore = aclAfter, etagAfter
myacl.Add(security.BlessingPattern("server/client"), string(access.Read))
if err := g.SetACL(ctx, myacl, ""); err != nil {
t.Fatal("SetACL failed: ", err)
}
// Check that the ACL and etag actually changed.
aclAfter, etagAfter = getACLAndEtagOrDie()
if !reflect.DeepEqual(myacl, aclAfter) {
t.Errorf("ACLs do not match: want %v, got %v", myacl, aclAfter)
}
if etagBefore == etagAfter {
t.Errorf("Etags should not match: %v", etagBefore)
}
// SetACL with unchanged ACL should succeed, and etag should still change.
aclBefore, etagBefore = aclAfter, etagAfter
if err := g.SetACL(ctx, myacl, ""); err != nil {
t.Fatal("SetACL failed: ", err)
}
// Check that the ACL did not change and the etag did change.
aclAfter, etagAfter = getACLAndEtagOrDie()
if !reflect.DeepEqual(aclBefore, aclAfter) {
t.Errorf("ACLs do not match: want %v, got %v", aclBefore, aclAfter)
}
if etagBefore == etagAfter {
t.Errorf("Etags should not match: %v", etagBefore)
}
// Take away our access. SetACL and GetACL should fail.
if err := g.SetACL(ctx, access.TaggedACLMap{}, ""); err != nil {
t.Fatal("SetACL failed: ", err)
}
if _, _, err := g.GetACL(ctx); !verror.Is(err, verror.ErrNoExistOrNoAccess.ID) {
t.Fatal("GetACL should have failed with access error")
}
if err := g.SetACL(ctx, myacl, ""); !verror.Is(err, verror.ErrNoExistOrNoAccess.ID) {
t.Fatal("SetACL should have failed with access error")
}
}
// Mirrors TestRemove.
func TestAdd(t *testing.T) {
ctx, serverName, cleanup := setupOrDie()
defer cleanup()
// Create a group with a default ACL and no entries.
g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
if err := g.Create(ctx, nil, nil); err != nil {
t.Fatal("Create failed: ", err)
}
// Verify entries of created group.
want, got := bpcSet(), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
var etagBefore, etagAfter string
etagBefore = getEtagOrDie(g, ctx, t)
// Add with bad etag should fail.
if err := g.Add(ctx, bpc("foo"), "20"); !verror.Is(err, verror.ErrBadEtag.ID) {
t.Fatal("Add should have failed with etag error")
}
// Etag should not have changed.
etagAfter = getEtagOrDie(g, ctx, t)
if etagBefore != etagAfter {
t.Errorf("Etags do not match: want %v, got %v", etagBefore, etagAfter)
}
// Add an entry, verify it was added and the etag changed.
etagBefore = etagAfter
if err := g.Add(ctx, bpc("foo"), etagBefore); err != nil {
t.Fatal("Add failed: ", err)
}
want, got = bpcSet("foo"), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
etagAfter = getEtagOrDie(g, ctx, t)
if etagBefore == etagAfter {
t.Errorf("Etags should not match: %v", etagBefore)
}
// Add another entry, verify it was added and the etag changed.
etagBefore = etagAfter
// Add with empty etag should succeed.
if err := g.Add(ctx, bpc("bar"), ""); err != nil {
t.Fatal("Add failed: ", err)
}
want, got = bpcSet("foo", "bar"), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
etagAfter = getEtagOrDie(g, ctx, t)
if etagBefore == etagAfter {
t.Errorf("Etags should not match: %v", etagBefore)
}
// Add "bar" again, verify entries are still ["foo", "bar"] and the etag
// changed.
etagBefore = etagAfter
if err := g.Add(ctx, bpc("bar"), etagBefore); err != nil {
t.Fatal("Add failed: ", err)
}
want, got = bpcSet("foo", "bar"), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
etagAfter = getEtagOrDie(g, ctx, t)
if etagBefore == etagAfter {
t.Errorf("Etags should not match: %v", etagBefore)
}
// Create a group with an ACL that disallows Add(), check that Add() fails.
g = groups.GroupClient(naming.JoinAddressName(serverName, "grpB"))
acl := access.TaggedACLMap{}
acl.Add(security.BlessingPattern("server/client"), string(access.Admin))
if err := g.Create(ctx, acl, nil); err != nil {
t.Fatal("Create failed: ", err)
}
// Add should fail (no access).
if err := g.Add(ctx, bpc("foo"), ""); !verror.Is(err, verror.ErrNoExistOrNoAccess.ID) {
t.Fatal("Add should have failed with access error")
}
}
// Mirrors TestAdd.
func TestRemove(t *testing.T) {
ctx, serverName, cleanup := setupOrDie()
defer cleanup()
// Create a group with a default ACL and two entries.
g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
if err := g.Create(ctx, nil, bpcSlice("foo", "bar")); err != nil {
t.Fatal("Create failed: ", err)
}
// Verify entries of created group.
want, got := bpcSet("foo", "bar"), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
var etagBefore, etagAfter string
etagBefore = getEtagOrDie(g, ctx, t)
// Remove with bad etag should fail.
if err := g.Remove(ctx, bpc("foo"), "20"); !verror.Is(err, verror.ErrBadEtag.ID) {
t.Fatal("Remove should have failed with etag error")
}
// Etag should not have changed.
etagAfter = getEtagOrDie(g, ctx, t)
if etagBefore != etagAfter {
t.Errorf("Etags do not match: want %v, got %v", etagBefore, etagAfter)
}
// Remove an entry, verify it was removed and the etag changed.
etagBefore = etagAfter
if err := g.Remove(ctx, bpc("foo"), etagBefore); err != nil {
t.Fatal("Remove failed: ", err)
}
want, got = bpcSet("bar"), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
etagAfter = getEtagOrDie(g, ctx, t)
if etagBefore == etagAfter {
t.Errorf("Etags should not match: %v", etagBefore)
}
// Remove another entry, verify it was removed and the etag changed.
etagBefore = etagAfter
// Remove with empty etag should succeed.
if err := g.Remove(ctx, bpc("bar"), ""); err != nil {
t.Fatal("Remove failed: ", err)
}
want, got = bpcSet(), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
etagAfter = getEtagOrDie(g, ctx, t)
if etagBefore == etagAfter {
t.Errorf("Etags should not match: %v", etagBefore)
}
// Remove "bar" again, verify entries are still [] and the etag changed.
etagBefore = etagAfter
if err := g.Remove(ctx, bpc("bar"), etagBefore); err != nil {
t.Fatal("Remove failed: ", err)
}
want, got = bpcSet(), getEntriesOrDie(g, ctx, t)
if !entriesEqual(want, got) {
t.Errorf("Entries do not match: want %v, got %v", want, got)
}
etagAfter = getEtagOrDie(g, ctx, t)
if etagBefore == etagAfter {
t.Errorf("Etags should not match: %v", etagBefore)
}
// Create a group with an ACL that disallows Remove(), check that Remove()
// fails.
g = groups.GroupClient(naming.JoinAddressName(serverName, "grpB"))
acl := access.TaggedACLMap{}
acl.Add(security.BlessingPattern("server/client"), string(access.Admin))
if err := g.Create(ctx, acl, bpcSlice("foo", "bar")); err != nil {
t.Fatal("Create failed: ", err)
}
// Remove should fail (no access).
if err := g.Remove(ctx, bpc("foo"), ""); !verror.Is(err, verror.ErrNoExistOrNoAccess.ID) {
t.Fatal("Remove should have failed with access error")
}
}
func TestGet(t *testing.T) {
// TODO(sadovsky): Implement.
}
func TestRest(t *testing.T) {
// TODO(sadovsky): Implement.
}