blob: 671757fc9a3044dd4d218f39f7f17c72516ba62f [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 syncbase_test
import (
"fmt"
"testing"
"v.io/v23/context"
"v.io/v23/security"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase"
"v.io/v23/syncbase"
"v.io/v23/verror"
_ "v.io/x/ref/runtime/factories/roaming"
tu "v.io/x/ref/services/syncbase/testutil"
)
type serviceTest struct {
f func(ctx *context.T, s syncbase.Service) error
}
type databaseTest struct {
f func(ctx *context.T, d syncbase.Database) error
}
type collectionTest struct {
f func(ctx *context.T, c syncbase.Collection) error
}
type rowTest struct {
f func(ctx *context.T, r syncbase.Row) error
}
type securitySpecTest struct {
layer interface{}
name string
pattern string
}
type securitySpecTestGroup struct {
layer interface{}
name string
patterns []string
mutating bool
}
// TODO(rogulenko): Add more test groups.
var securitySpecTestGroups = []securitySpecTestGroup{
// Service tests.
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
_, err := wire.ServiceClient(s.FullName()).DevModeGetTime(ctx)
return err
}},
name: "service.DevModeGetTime",
patterns: []string{"A___"},
},
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
return wire.ServiceClient(s.FullName()).DevModeUpdateVClock(ctx, wire.DevModeUpdateVClockOpts{})
}},
name: "service.DevModeUpdateVClock",
patterns: []string{"A___"},
},
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
_, _, err := s.GetPermissions(ctx)
return err
}},
name: "service.GetPermissions",
patterns: []string{"A___"},
},
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
return s.SetPermissions(ctx, nil, "")
}},
name: "service.SetPermissions",
patterns: []string{"A___"},
mutating: true,
},
// Database tests.
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
return s.DatabaseForId(wire.Id{"a", "dNew"}, nil).Create(ctx, nil)
}},
name: "database.Create",
patterns: []string{"W___"},
mutating: true,
},
{
layer: databaseTest{f: func(ctx *context.T, d syncbase.Database) error {
return d.Destroy(ctx)
}},
name: "database.Destroy",
patterns: []string{"_W__"},
mutating: true,
},
{
layer: databaseTest{f: func(ctx *context.T, d syncbase.Database) error {
_, _, err := d.GetPermissions(ctx)
return err
}},
name: "database.GetPermissions",
patterns: []string{"_A__"},
},
{
layer: databaseTest{f: func(ctx *context.T, d syncbase.Database) error {
return d.SetPermissions(ctx, nil, "")
}},
name: "database.SetPermissions",
patterns: []string{"_A__"},
mutating: true,
},
// Collection tests.
{
layer: collectionTest{f: func(ctx *context.T, c syncbase.Collection) error {
_, err := c.GetPermissions(ctx)
return err
}},
name: "collection.GetPermissions",
patterns: []string{"__A_"},
},
// Row tests.
{
layer: rowTest{f: func(ctx *context.T, r syncbase.Row) error {
var value string
return r.Get(ctx, &value)
}},
name: "row.Get",
patterns: []string{"__R_"},
},
}
// TestSecuritySpec tests the Syncbase ACL specification. It runs a set of test
// groups.
//
// Each test group describes a Syncbase public method and a list of security
// patterns that should allow a client to call the method.
// A method might be service.GetPermissions and the pattern for it is "A___".
// A security pattern is a string of 4 bytes where each byte is an '_' or
// one of X (resolve), R (read), W (write), A (admin).
// The character index stands for:
// 0 - service ACL
// 1 - database ACL
// 2 - collection ACL
// 3 - syncgroup ACL
// A pattern defines a set of per-ACL permissions required for a client to call
// the method.
//
// For each test group we generate two sets of tests: allowed tests and denied
// tests.
// Allowed tests are generated by splitting the list of security patterns
// of the test group into one pattern per test.
// Denied tests are generated the following way: we take each allowed pattern
// and generate all patterns that differ in one character, preserving all '_'.
// This way we make sure that every character in a security pattern is
// important.
// Allowed tests are split in two categories: mutating tests and static tests.
// Mutating tests change the state of Syncbase, static don't.
// Denied tests are always static.
//
// Then we pack all tests into runs, where each run has only one type of tests
// (allowed or denied) and at most one mutating test. For each run we rebuild
// the following Syncbase structure:
// service s; database {a,d}; collection c; row prefix.
//
// For each test inside a run we generate 5 huge ACLs with a record for each
// of the test + one admin record.
func TestSecuritySpec(t *testing.T) {
deniedTests, staticAllowedTests, mutatingAllowedTests := prepareTests()
runTests(t, false, deniedTests...)
runTests(t, true, staticAllowedTests...)
for _, test := range mutatingAllowedTests {
runTests(t, true, test)
}
}
func prepareTests() (deniedTests, staticAllowedTests, mutatingAllowedTests []securitySpecTest) {
for _, testGroup := range securitySpecTestGroups {
// Collect allowed patterns.
allowedPatterns := map[string]bool{}
for _, pattern := range testGroup.patterns {
allowedPatterns[pattern] = true
}
// Fill in allowed test slices.
for pattern := range allowedPatterns {
test := securitySpecTest{
layer: testGroup.layer,
name: testGroup.name,
pattern: pattern,
}
if testGroup.mutating {
mutatingAllowedTests = append(mutatingAllowedTests, test)
} else {
staticAllowedTests = append(staticAllowedTests, test)
}
}
// Collect denied patterns.
deniedPatterns := map[string]bool{}
for _, pattern := range testGroup.patterns {
for i := 0; i < len(pattern); i++ {
if pattern[i] != '_' {
patternBytes := []byte(pattern)
for _, c := range []byte{'X', 'R', 'W', 'A'} {
patternBytes[i] = c
if !allowedPatterns[string(patternBytes)] {
deniedPatterns[string(patternBytes)] = true
}
}
}
}
}
// Fill in denied test slice.
for pattern := range deniedPatterns {
test := securitySpecTest{
layer: testGroup.layer,
name: testGroup.name,
pattern: pattern,
}
deniedTests = append(deniedTests, test)
}
}
return
}
func runTests(t *testing.T, expectSuccess bool, tests ...securitySpecTest) {
// Create permissions.
servicePerms := tu.DefaultPerms("root:admin")
addPerms(servicePerms, 0, tests...)
databasePerms := tu.DefaultPerms("root:admin")
addPerms(databasePerms, 1, tests...)
collectionPerms := tu.DefaultPerms("root:admin")
addPerms(collectionPerms, 2, tests...)
sgPerms := tu.DefaultPerms("root:admin")
addPerms(sgPerms, 3, tests...)
// Create service/database/collection/row with permissions above.
ctx, adminCtx, sName, rootp, cleanup := tu.SetupOrDieCustom("admin", "server", nil)
defer cleanup()
s := syncbase.NewService(sName)
if err := s.SetPermissions(adminCtx, servicePerms, ""); err != nil {
tu.Fatalf(t, "s.SetPermissions failed: %v", err)
}
d := s.DatabaseForId(wire.Id{"a", "d"}, nil)
if err := d.Create(adminCtx, databasePerms); err != nil {
tu.Fatalf(t, "d.Create failed: %v", err)
}
c := d.CollectionForId(wire.Id{"u", "c"})
if err := c.Create(adminCtx, collectionPerms); err != nil {
tu.Fatalf(t, "c.Create failed: %v", err)
}
r := c.Row("prefix")
r.Put(adminCtx, "value")
// Verify tests.
for i, test := range tests {
clientCtx := tu.NewCtx(ctx, rootp, fmt.Sprintf("client%d", i))
var err error
switch layer := test.layer.(type) {
case serviceTest:
err = layer.f(clientCtx, s)
case databaseTest:
err = layer.f(clientCtx, d)
case collectionTest:
err = layer.f(clientCtx, c)
case rowTest:
err = layer.f(clientCtx, r)
}
if expectSuccess && err != nil {
tu.Fatalf(t, "test %v failed with non-nil error: %v", test, err)
} else if !expectSuccess && verror.ErrorID(err) != verror.ErrNoAccess.ID {
tu.Fatalf(t, "test %v didn't fail with ErrNoAccess error: %v", test, err)
}
}
}
// addPerms add permissions to the perms object for each test.
// For each test we add a blessing pattern "root:clientXX" with a tag
// corresponding test.pattern[index].
func addPerms(perms access.Permissions, index int, tests ...securitySpecTest) {
tagsMap := map[byte]string{
'X': "Resolve",
'R': "Read",
'W': "Write",
'A': "Admin",
}
for i, t := range tests {
if tag, ok := tagsMap[t.pattern[index]]; ok {
perms.Add(security.BlessingPattern(fmt.Sprintf("root:client%d", i)), tag)
}
}
}