blob: 4c479e32438c06515b9a60db277367fe8b0e73fc [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 internal_test
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/verror"
_ "v.io/x/ref/profiles"
vsecurity "v.io/x/ref/security"
"v.io/x/ref/services/role"
irole "v.io/x/ref/services/role/roled/internal"
"v.io/x/ref/test/testutil"
)
func TestSeekBlessings(t *testing.T) {
ctx, shutdown := v23.Init()
defer shutdown()
workdir, err := ioutil.TempDir("", "test-role-server-")
if err != nil {
t.Fatal("ioutil.TempDir failed: %v", err)
}
defer os.RemoveAll(workdir)
// Role A is a restricted role, i.e. it can be used in sensitive ACLs.
roleAConf := irole.Config{
Members: []security.BlessingPattern{
"root/users/user1/_role",
"root/users/user2/_role",
"root/users/user3", // _role implied
},
Extend: true,
}
irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "A.conf"))
// Role B is an unrestricted role.
roleBConf := irole.Config{
Members: []security.BlessingPattern{
"root/users/user1/_role",
"root/users/user3/_role",
},
Audit: true,
Extend: false,
}
irole.WriteConfig(t, roleBConf, filepath.Join(workdir, "B.conf"))
root := testutil.NewIDProvider("root")
var (
user1 = newPrincipalContext(t, ctx, root, "users/user1")
user1R = newPrincipalContext(t, ctx, root, "users/user1/_role")
user2 = newPrincipalContext(t, ctx, root, "users/user2")
user2R = newPrincipalContext(t, ctx, root, "users/user2/_role")
user3 = newPrincipalContext(t, ctx, root, "users/user3")
user3R = newPrincipalContext(t, ctx, root, "users/user3", "users/user3/_role/foo", "users/user3/_role/bar")
)
testServerCtx := newPrincipalContext(t, ctx, root, "testserver")
server, testAddr := newServer(t, testServerCtx)
tDisp := &testDispatcher{}
if err := server.ServeDispatcher("", tDisp); err != nil {
t.Fatalf("server.ServeDispatcher failed: %v", err)
}
const noErr = ""
testcases := []struct {
ctx *context.T
role string
errID verror.ID
blessings []string
}{
{user1, "", verror.ErrNoExist.ID, nil},
{user1, "unknown", verror.ErrNoAccess.ID, nil},
{user2, "unknown", verror.ErrNoAccess.ID, nil},
{user3, "unknown", verror.ErrNoAccess.ID, nil},
{user1, "A", verror.ErrNoAccess.ID, nil},
{user1R, "A", noErr, []string{"root/roles/A/root/users/user1"}},
{user2, "A", verror.ErrNoAccess.ID, nil},
{user2R, "A", noErr, []string{"root/roles/A/root/users/user2"}},
{user3, "A", verror.ErrNoAccess.ID, nil},
{user3R, "A", noErr, []string{"root/roles/A/root/users/user3/_role/bar", "root/roles/A/root/users/user3/_role/foo"}},
{user1, "B", verror.ErrNoAccess.ID, nil},
{user1R, "B", noErr, []string{"root/roles/B"}},
{user2, "B", verror.ErrNoAccess.ID, nil},
{user2R, "B", verror.ErrNoAccess.ID, nil},
{user3, "B", verror.ErrNoAccess.ID, nil},
{user3R, "B", noErr, []string{"root/roles/B"}},
}
addr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)
for _, tc := range testcases {
user := v23.GetPrincipal(tc.ctx).BlessingStore().Default()
c := role.RoleClient(naming.Join(addr, tc.role))
blessings, err := c.SeekBlessings(tc.ctx)
if verror.ErrorID(err) != tc.errID {
t.Errorf("unexpected error ID for (%q, %q). Got %#v, expected %#v", user, tc.role, verror.ErrorID(err), tc.errID)
}
if err == nil {
previousBlessings, _ := v23.GetPrincipal(tc.ctx).BlessingStore().Set(blessings, security.AllPrincipals)
blessingNames, rejected := callTest(t, tc.ctx, testAddr)
if !reflect.DeepEqual(blessingNames, tc.blessings) {
t.Errorf("unexpected blessings for (%q, %q). Got %q, expected %q", user, tc.role, blessingNames, tc.blessings)
}
if len(rejected) != 0 {
t.Errorf("unexpected rejected blessings for (%q, %q): %q", user, tc.role, rejected)
}
v23.GetPrincipal(tc.ctx).BlessingStore().Set(previousBlessings, security.AllPrincipals)
}
}
}
func TestGlob(t *testing.T) {
ctx, shutdown := v23.Init()
defer shutdown()
workdir, err := ioutil.TempDir("", "test-role-server-")
if err != nil {
t.Fatal("ioutil.TempDir failed: %v", err)
}
defer os.RemoveAll(workdir)
os.Mkdir(filepath.Join(workdir, "sub1"), 0700)
os.Mkdir(filepath.Join(workdir, "sub1", "sub2"), 0700)
os.Mkdir(filepath.Join(workdir, "sub3"), 0700)
// Role that user1 has access to.
roleAConf := irole.Config{Members: []security.BlessingPattern{"root/user1"}}
irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "A.conf"))
irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/B.conf"))
irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/C.conf"))
irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/sub2/D.conf"))
// Role that user2 has access to.
roleBConf := irole.Config{Members: []security.BlessingPattern{"root/user2"}}
irole.WriteConfig(t, roleBConf, filepath.Join(workdir, "sub1/sub2/X.conf"))
root := testutil.NewIDProvider("root")
user1 := newPrincipalContext(t, ctx, root, "user1/_role")
user2 := newPrincipalContext(t, ctx, root, "user2/_role")
user3 := newPrincipalContext(t, ctx, root, "user3/_role")
addr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)
testcases := []struct {
user *context.T
name string
pattern string
results []string
}{
{user1, "", "*", []string{"A", "sub1"}},
{user1, "sub1", "*", []string{"B", "C", "sub2"}},
{user1, "sub1/sub2", "*", []string{"D"}},
{user1, "", "...", []string{"", "A", "sub1", "sub1/B", "sub1/C", "sub1/sub2", "sub1/sub2/D"}},
{user2, "", "*", []string{"sub1"}},
{user2, "", "...", []string{"", "sub1", "sub1/sub2", "sub1/sub2/X"}},
{user3, "", "*", []string{}},
{user3, "", "...", []string{""}},
}
for i, tc := range testcases {
matches, _, _ := testutil.GlobName(tc.user, naming.Join(addr, tc.name), tc.pattern)
if !reflect.DeepEqual(matches, tc.results) {
t.Errorf("unexpected results for tc #%d. Got %q, expected %q", i, matches, tc.results)
}
}
}
func newPrincipalContext(t *testing.T, ctx *context.T, root *testutil.IDProvider, names ...string) *context.T {
principal := testutil.NewPrincipal()
var blessings []security.Blessings
for _, n := range names {
blessing, err := root.NewBlessings(principal, n)
if err != nil {
t.Fatal("root.Bless failed for %q: %v", n, err)
}
blessings = append(blessings, blessing)
}
bUnion, err := security.UnionOfBlessings(blessings...)
if err != nil {
t.Fatal("security.UnionOfBlessings failed: %v", err)
}
vsecurity.SetDefaultBlessings(principal, bUnion)
ctx, err = v23.SetPrincipal(ctx, principal)
if err != nil {
t.Fatal("v23.SetPrincipal failed: %v", err)
}
return ctx
}
func newRoleServer(t *testing.T, ctx *context.T, dir string) string {
server, addr := newServer(t, ctx)
if err := server.ServeDispatcher("", irole.NewDispatcher(dir, addr)); err != nil {
t.Fatalf("ServeDispatcher failed: %v", err)
}
return addr
}
func newServer(t *testing.T, ctx *context.T) (rpc.Server, string) {
server, err := v23.NewServer(ctx)
if err != nil {
t.Fatalf("NewServer() failed: %v", err)
}
spec := rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
endpoints, err := server.Listen(spec)
if err != nil {
t.Fatalf("Listen(%v) failed: %v", spec, err)
}
return server, endpoints[0].Name()
}
func callTest(t *testing.T, ctx *context.T, addr string) (blessingNames []string, rejected []security.RejectedBlessing) {
call, err := v23.GetClient(ctx).StartCall(ctx, addr, "Test", nil)
if err != nil {
t.Fatalf("StartCall failed: %v", err)
}
if err := call.Finish(&blessingNames, &rejected); err != nil {
t.Fatalf("Finish failed: %v", err)
}
return
}
type testDispatcher struct {
}
func (d *testDispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
return d, d, nil
}
func (d *testDispatcher) Authorize(*context.T) error {
return nil
}
func (d *testDispatcher) Test(call rpc.ServerCall) ([]string, []security.RejectedBlessing, error) {
blessings, rejected := security.RemoteBlessingNames(call.Context())
return blessings, rejected, nil
}