blob: c25bfc11f460eba4fcf8d778b9efce7cf826e7bc [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main_test
import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"testing"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/security/access"
"v.io/v23/services/groups"
"v.io/v23/verror"
"v.io/x/lib/gosh"
"v.io/x/lib/set"
"v.io/x/ref/lib/signals"
"v.io/x/ref/services/groups/groupsd/testdata/kvstore"
"v.io/x/ref/test"
"v.io/x/ref/test/expect"
"v.io/x/ref/test/v23test"
)
type relateResult struct {
Remainder map[string]struct{}
Approximations []groups.Approximation
Version string
}
// TestV23GroupServerIntegration tests the integration between the "groups"
// command-line client and the "groupsd" server.
func TestV23GroupServerIntegration(t *testing.T) {
v23test.SkipUnlessRunningIntegrationTests(t)
sh := v23test.NewShell(t, nil)
defer sh.Cleanup()
sh.StartRootMountTable()
// Build binaries for the client and server.
var (
clientBin = v23test.BuildGoPkg(sh, "v.io/x/ref/services/groups/groups")
serverBin = v23test.BuildGoPkg(sh, "v.io/x/ref/services/groups/groupsd", "-tags=leveldb")
serverName = "groups-server"
groupA = naming.Join(serverName, "groupA")
groupB = naming.Join(serverName, "groupB")
)
// Start the groups server.
sh.Cmd(serverBin, "-name="+serverName, "-v23.tcp.address=127.0.0.1:0").Start()
// Create a couple of groups.
sh.Cmd(clientBin, "create", groupA).Run()
sh.Cmd(clientBin, "create", groupB, "a", "a:b").Run()
// Add a couple of blessing patterns.
sh.Cmd(clientBin, "add", groupA, "<grp:groups-server/groupB>").Run()
sh.Cmd(clientBin, "add", groupA, "a").Run()
sh.Cmd(clientBin, "add", groupB, "a:b:c").Run()
// Remove a blessing pattern.
sh.Cmd(clientBin, "remove", groupB, "a").Run()
// Test simple group resolution.
{
stdout, stderr := sh.Cmd(clientBin, "relate", groupB, "a:b:c:d").StdoutStderr()
var got relateResult
if err := json.Unmarshal([]byte(stdout), &got); err != nil {
t.Fatalf("Unmarshal(%v) failed: %v", stdout, err)
}
want := relateResult{
Remainder: set.String.FromSlice([]string{"c:d", "d"}),
Approximations: nil,
Version: "2",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
if got, want := stderr, ""; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// Test recursive group resolution.
{
stdout, stderr := sh.Cmd(clientBin, "relate", groupA, "a:b:c:d").StdoutStderr()
var got relateResult
if err := json.Unmarshal([]byte(stdout), &got); err != nil {
t.Fatalf("Unmarshal(%v) failed: %v", stdout, err)
}
want := relateResult{
Remainder: set.String.FromSlice([]string{"b:c:d", "c:d", "d"}),
Approximations: nil,
Version: "2",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
if got, want := stderr, ""; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// Test group resolution failure. Note that under-approximation is
// used as the default to handle resolution failures.
{
sh.Cmd(clientBin, "add", groupB, "<grp:groups-server/groupC>").Run()
stdout, stderr := sh.Cmd(clientBin, "relate", groupB, "a:b:c:d").StdoutStderr()
var got relateResult
if err := json.Unmarshal([]byte(stdout), &got); err != nil {
t.Fatalf("Unmarshal(%v) failed: %v", stdout, err)
}
want := relateResult{
Remainder: set.String.FromSlice([]string{"c:d", "d"}),
Approximations: []groups.Approximation{
groups.Approximation{
Reason: "v.io/v23/verror.NoExist",
Details: `groupsd:"groupC".Relate: Does not exist: groupC`,
},
},
Version: "3",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
if got, want := stderr, ""; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// Delete the groups.
sh.Cmd(clientBin, "delete", groupA).Run()
sh.Cmd(clientBin, "delete", groupB).Run()
}
// store implements the kvstore.Store interface.
type store map[string]string
func (s store) Get(ctx *context.T, call rpc.ServerCall, key string) (string, error) {
return s[key], nil
}
func (s store) Set(ctx *context.T, call rpc.ServerCall, key string, value string) error {
s[key] = value
return nil
}
const (
kvServerName = "key-value-store"
getFailed = "GET FAILED"
getOK = "GET OK"
setFailed = "SET FAILED"
setOK = "SET OK"
)
var runServer = gosh.RegisterFunc("server", func() error {
ctx, shutdown := test.V23Init()
defer shutdown()
// Use a shorter timeout to reduce the test overall runtime as the permissions
// authorizer will attempt to connect to a non-existing groups server at some
// point in the test.
ctx, _ = context.WithTimeout(ctx, 2*time.Second)
authorizer, err := groups.PermissionsAuthorizer(access.Permissions{
"Read": access.AccessList{In: []security.BlessingPattern{"<grp:groups-server/readers>"}},
"Write": access.AccessList{In: []security.BlessingPattern{"<grp:groups-server/writers>"}},
}, access.TypicalTagType())
if err != nil {
return err
}
if _, _, err := v23.WithNewServer(ctx, kvServerName, kvstore.StoreServer(&store{}), authorizer); err != nil {
return err
}
<-signals.ShutdownOnSignals(ctx)
return nil
})
var runClient = gosh.RegisterFunc("client", func(command string, args ...string) error {
ctx, shutdown := test.V23Init()
defer shutdown()
client := kvstore.StoreClient(kvServerName)
switch command {
case "get":
if got, want := len(args), 1; got != want {
return fmt.Errorf("unexpected number of arguments: got %v, want %v", got, want)
}
key := args[0]
value, err := client.Get(ctx, key)
if err != nil {
fmt.Printf("%v %v\n", getFailed, verror.ErrorID(err))
} else {
fmt.Printf("%v %v\n", getOK, value)
}
case "set":
if got, want := len(args), 2; got != want {
return fmt.Errorf("unexpected number of arguments: got %v, want %v", got, want)
}
key, value := args[0], args[1]
if err := client.Set(ctx, key, value); err != nil {
fmt.Printf("%v %v\n", setFailed, verror.ErrorID(err))
} else {
fmt.Printf("%v\n", setOK)
}
}
return nil
})
func startClient(sh *v23test.Shell, name string, args ...interface{}) *expect.Session {
cmd := sh.FuncCmd(runClient, args...).WithCredentials(sh.ForkCredentials(name))
cmd.Start()
return cmd.S
}
// TestV23GroupServerAuthorization uses an instance of the KeyValueStore server
// with an groups-based authorizer to test the group server implementation.
func TestV23GroupServerAuthorization(t *testing.T) {
v23test.SkipUnlessRunningIntegrationTests(t)
sh := v23test.NewShell(t, nil)
defer sh.Cleanup()
sh.StartRootMountTable()
// Build binaries for the groups client and server.
var (
clientBin = v23test.BuildGoPkg(sh, "v.io/x/ref/services/groups/groups")
serverBin = v23test.BuildGoPkg(sh, "v.io/x/ref/services/groups/groupsd")
serverName = "groups-server"
readers = naming.Join(serverName, "readers")
writers = naming.Join(serverName, "writers")
)
// Start the groups server.
server := sh.Cmd(serverBin, "-name="+serverName, "-v23.tcp.address=127.0.0.1:0")
server.Start()
// Create a couple of groups. The <readers> and <writers> groups
// identify blessings that can be used to read from and write to the
// key value store server respectively.
sh.Cmd(clientBin, "create", readers, "root:alice", "root:bob").Run()
sh.Cmd(clientBin, "create", writers, "root:alice").Run()
// Start an instance of the key value store server.
sh.FuncCmd(runServer).Start()
// Test that alice can write.
startClient(sh, "alice", "set", "foo", "bar").Expect(setOK)
// Test that alice can read.
startClient(sh, "alice", "get", "foo").Expectf("%v %v", getOK, "bar")
// Test that bob can read.
startClient(sh, "bob", "get", "foo").Expectf("%v %v", getOK, "bar")
// Test that bob cannot write.
startClient(sh, "bob", "set", "foo", "bar").Expectf("%v %v", setFailed, verror.ErrNoAccess.ID)
// Stop the groups server and check that as a consequence "alice"
// cannot read from the key value store server anymore.
server.Terminate(os.Interrupt)
// Use gosh.Cmd directly instead of startClient().Expectf because
// startClient returns an expect.Session that has a default timeout
// that sometimes interferes with the RPC and resolution timeouts
// causing this test to be flaky.
output := sh.FuncCmd(runClient, "get", "foo").WithCredentials(sh.ForkCredentials("alice")).CombinedOutput()
if got, want := output, fmt.Sprint(getFailed, " ", verror.ErrNoAccess.ID); !strings.HasPrefix(got, want) {
t.Errorf("Got %q, wanted to start with %q", got, want)
}
}
func TestMain(m *testing.M) {
v23test.TestMain(m)
}