| // 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 nosql_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/syncbase/nosql" |
| "v.io/v23/verror" |
| _ "v.io/x/ref/runtime/factories/generic" |
| tu "v.io/x/ref/services/syncbase/testutil" |
| ) |
| |
| type serviceTest struct { |
| f func(ctx *context.T, s syncbase.Service) error |
| } |
| |
| type appTest struct { |
| f func(ctx *context.T, a syncbase.App) error |
| } |
| |
| type databaseTest struct { |
| f func(ctx *context.T, db nosql.Database) error |
| } |
| |
| type tableTest struct { |
| f func(ctx *context.T, tb nosql.Table) error |
| } |
| |
| type rowTest struct { |
| f func(ctx *context.T, r nosql.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, |
| }, |
| |
| // App tests. |
| { |
| layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error { |
| return s.App("newApp").Create(ctx, nil) |
| }}, |
| name: "app.Create", |
| patterns: []string{"W_____"}, |
| mutating: true, |
| }, |
| { |
| layer: appTest{f: func(ctx *context.T, a syncbase.App) error { |
| return a.Destroy(ctx) |
| }}, |
| name: "app.Destroy", |
| patterns: []string{"_W____"}, |
| mutating: true, |
| }, |
| { |
| layer: appTest{f: func(ctx *context.T, a syncbase.App) error { |
| _, _, err := a.GetPermissions(ctx) |
| return err |
| }}, |
| name: "app.GetPermissions", |
| patterns: []string{"_A____"}, |
| }, |
| { |
| layer: appTest{f: func(ctx *context.T, a syncbase.App) error { |
| return a.SetPermissions(ctx, nil, "") |
| }}, |
| name: "app.SetPermissions", |
| patterns: []string{"_A____"}, |
| mutating: true, |
| }, |
| |
| // Database tests. |
| { |
| layer: databaseTest{f: func(ctx *context.T, db nosql.Database) error { |
| _, _, err := db.GetPermissions(ctx) |
| return err |
| }}, |
| name: "database.GetPermissions", |
| patterns: []string{"__A___"}, |
| }, |
| |
| // Table tests. |
| { |
| layer: tableTest{f: func(ctx *context.T, tb nosql.Table) error { |
| _, err := tb.GetPermissions(ctx) |
| return err |
| }}, |
| name: "table.GetPermissions", |
| patterns: []string{"___A__"}, |
| }, |
| |
| // Row tests. |
| { |
| layer: rowTest{f: func(ctx *context.T, r nosql.Row) error { |
| var value string |
| return r.Get(ctx, &value) |
| }}, |
| name: "row.Get", |
| patterns: []string{"___RR_"}, |
| }, |
| } |
| |
| // 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 6 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 - app ACL |
| // 2 - database ACL |
| // 3 - table ACL |
| // 4 - longest prefix ACL |
| // 5 - 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; app a; database db; table tb; row prefix (with ACL at 'prefix'). |
| // |
| // For each test inside a run we generate 6 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...) |
| appPerms := tu.DefaultPerms("root:admin") |
| addPerms(appPerms, 1, tests...) |
| databasePerms := tu.DefaultPerms("root:admin") |
| addPerms(databasePerms, 2, tests...) |
| tablePerms := tu.DefaultPerms("root:admin") |
| addPerms(tablePerms, 3, tests...) |
| rowPerms := tu.DefaultPerms("root:admin") |
| addPerms(rowPerms, 4, tests...) |
| sgPerms := tu.DefaultPerms("root:admin") |
| addPerms(sgPerms, 5, tests...) |
| |
| // Create service/app/database/table/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) |
| } |
| a := s.App("a") |
| if err := a.Create(adminCtx, appPerms); err != nil { |
| tu.Fatalf(t, "a.Create failed: %v", err) |
| } |
| db := a.NoSQLDatabase("db", nil) |
| if err := db.Create(adminCtx, databasePerms); err != nil { |
| tu.Fatalf(t, "db.Create failed: %v", err) |
| } |
| tb := db.Table("tb") |
| if err := tb.Create(adminCtx, tablePerms); err != nil { |
| tu.Fatalf(t, "db.Create failed: %v", err) |
| } |
| if err := tb.SetPrefixPermissions(adminCtx, nosql.Prefix(""), rowPerms); err != nil { |
| tu.Fatalf(t, "tb.SetPrefixPermissions failed: %v", err) |
| } |
| if err := tb.SetPrefixPermissions(adminCtx, nosql.Prefix("prefix"), rowPerms); err != nil { |
| tu.Fatalf(t, "tb.SetPrefixPermissions failed: %v", err) |
| } |
| r := tb.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 appTest: |
| err = layer.f(clientCtx, a) |
| case databaseTest: |
| err = layer.f(clientCtx, db) |
| case tableTest: |
| err = layer.f(clientCtx, tb) |
| 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) |
| } |
| } |
| } |