| // 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) |
| } |
| } |
| } |