| // 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 groups |
| |
| import ( |
| "errors" |
| "reflect" |
| "testing" |
| |
| "v.io/v23/context" |
| "v.io/v23/security" |
| ) |
| |
| var ( |
| errGrpNotFound = errors.New("group not found") |
| ) |
| |
| func TestSplitPattern(t *testing.T) { |
| valid := []struct { |
| Pattern security.BlessingPattern |
| Parts []string |
| }{ |
| { |
| Pattern: "a:b:c", |
| Parts: []string{"a", "b", "c"}, |
| }, |
| { |
| Pattern: "a", |
| Parts: []string{"a"}, |
| }, |
| { |
| Pattern: "a:<grp:1/2/3>:c", |
| Parts: []string{"a", "<grp:1/2/3>", "c"}, |
| }, |
| { |
| Pattern: "a:<grp:1/2/3>:c:<grp:4/5/6>", |
| Parts: []string{"a", "<grp:1/2/3>", "c", "<grp:4/5/6>"}, |
| }, |
| { |
| Pattern: "a:<grp:1/2/3>:<grp:4/5/6>:<grp:/7/8/9>", |
| Parts: []string{"a", "<grp:1/2/3>", "<grp:4/5/6>", "<grp:/7/8/9>"}, |
| }, |
| { |
| Pattern: "<grp:1/2/3>", |
| Parts: []string{"<grp:1/2/3>"}, |
| }, |
| { |
| Pattern: "<grp:1/2/3<grp:>", |
| Parts: []string{"<grp:1/2/3<grp:>"}, |
| }, |
| } |
| |
| invalid := []security.BlessingPattern{"a:UV<grp:1/2/3>:c:<grp:4/5/6>WXY", "a:<grp:1/2/3><grp:4/5/6>", "a:<grp:1/2/3<>grp:4/5/6>", "<grp:1/2/3>:", "a:UV<grp:1/2/3>", "a:<grp:>:b"} |
| |
| for _, test := range valid { |
| want := test.Parts |
| got, err := splitPattern(test.Pattern) |
| if err != nil { |
| t.Errorf("%q.splitPattern() return err %v", test.Pattern, err) |
| } |
| if !reflect.DeepEqual(got, want) { |
| t.Errorf("%q.splitPattern() got %v want %v", test.Pattern, got, want) |
| } |
| } |
| |
| for _, test := range invalid { |
| if got, err := splitPattern(test); err == nil { |
| t.Errorf("%q.splitPattern() got %v want nil", test, got) |
| } |
| } |
| } |
| |
| // Portion of test copied from v23/security/pattern_test.go. |
| func TestIsValid(t *testing.T) { |
| var ( |
| valid = []security.BlessingPattern{security.AllPrincipals, "alice", "al$ice", "alice:$", "alice.jones:$", "alice@google:$", "v23:alice@google:$", "v23:alice@google:bob:$", "alice", "alice:bob", "alice:bob:<grp:vndm/bob/allmydevs>:app1", "alice:bob:<grp:vndm/bob/allmydevs>:<grp:vndm/bob/corpapps>", "<grp:vndm/bob/allmydevs>", "<grp:vndm/bob/allmydevs>:app1"} |
| |
| invalid = []security.BlessingPattern{"", "alice...", "...alice", "alice...bob", ":alice", "alice:", "...alice:bob", "alice...:bob", "alice:...:bob", "alice:$:bob", "alice:$:$", "alice:...:$", "alice:...", "alice<", "alice<>", "alice>", "alice<grp:>", "alice:<grp:vndm/bob/allmydevs<>grp:vndm/bob/corpapps>", "alice:UV<grp:vndm/bob/allmydevs>:c:<grp:vndm/bob/corpapps>WXY", "a:<grp:vndm/bob/corpapps><grp:vndm/bob/allmydevs>", "alice:<grp:>"} |
| ) |
| for _, p := range valid { |
| if _, err := splitPattern(p); err != nil { |
| t.Errorf("%q.splitPattern() returned error %v", p, err) |
| } |
| } |
| for _, p := range invalid { |
| if _, err := splitPattern(p); err == nil { |
| t.Errorf("%q.splitPattern() returned nil", p) |
| } |
| } |
| } |
| |
| // Test copied from v23/security/pattern_test.go. |
| func TestMatch(t *testing.T) { |
| type v []string |
| valid := []struct { |
| Pattern security.BlessingPattern |
| Matches v |
| Remainder v // Remainder[i] is the result of match on Pattern and Matches[i]. |
| DoesNotMatch v |
| }{ |
| { |
| Pattern: "", |
| DoesNotMatch: v{"", "ann", "bob", "ann:friend"}, |
| }, |
| { |
| Pattern: "$", |
| DoesNotMatch: v{"$", "ann", "bob", "ann:friend"}, |
| }, |
| { |
| Pattern: security.AllPrincipals, |
| Matches: v{"", "ann", "bob", "ann:friend"}, |
| Remainder: v{"", "", "", ""}, |
| }, |
| { |
| Pattern: "ann", |
| Matches: v{"ann", "ann:friend", "ann:enemy", "ann:friend:spouse"}, |
| Remainder: v{"", "friend", "enemy", "friend:spouse"}, |
| DoesNotMatch: v{"", "bob", "bob:ann"}, |
| }, |
| { |
| Pattern: "ann:friend", |
| Matches: v{"ann:friend", "ann:friend:spouse"}, |
| Remainder: v{"", "spouse"}, |
| DoesNotMatch: v{"", "ann", "ann:enemy", "bob", "bob:ann"}, |
| }, |
| { |
| Pattern: "ann:friend:$", |
| Matches: v{"ann:friend"}, |
| Remainder: v{""}, |
| DoesNotMatch: v{"", "ann", "ann:enemy", "ann:friend:spouse", "bob", "bob:friend", "bob:ann"}, |
| }, |
| } |
| |
| g := &grpClient{} |
| for _, test := range valid { |
| if len(test.Matches) != len(test.Remainder) { |
| t.Errorf("invalid test case %v", test) |
| } |
| |
| for i := 0; i < len(test.Matches); i++ { |
| if remainder := g.match(nil, test.Pattern, convertToSet(test.Matches[i])); len(remainder) != 1 || !contains(remainder, test.Remainder[i]) || g.apprxs != nil { |
| t.Errorf("%q.match(%v) got %v, want %v, errs %v", test.Pattern, test.Matches[i], remainder, test.Remainder[i], g.apprxs) |
| } |
| } |
| |
| if len(test.Matches) != 0 { |
| remainderWant := convertToSet(test.Remainder...) |
| if remainderGot := g.match(nil, test.Pattern, convertToSet(test.Matches...)); !reflect.DeepEqual(remainderGot, remainderWant) || g.apprxs != nil { |
| t.Errorf("%q.match(%v) got %v, want %v, errs %v", test.Pattern, test.Matches, remainderGot, remainderWant, g.apprxs) |
| } |
| } |
| |
| for i := 0; i < len(test.DoesNotMatch); i++ { |
| if remainder := g.match(nil, test.Pattern, convertToSet(test.DoesNotMatch[i])); len(remainder) != 0 || g.apprxs != nil { |
| t.Errorf("%q.match(%v) returned remainder %v, errs %v", test.Pattern, test.DoesNotMatch[i], remainder, g.apprxs) |
| } |
| } |
| |
| if len(test.DoesNotMatch) != 0 { |
| if remainder := g.match(nil, test.Pattern, convertToSet(test.DoesNotMatch...)); len(remainder) != 0 || g.apprxs != nil { |
| t.Errorf("%q.match(%v) returned remainder %v, errs %v", test.Pattern, test.DoesNotMatch, remainder, g.apprxs) |
| } |
| } |
| } |
| |
| invalid := []struct { |
| Pattern security.BlessingPattern |
| Matches v |
| Remainder v // Remainder[i] is the result of match on Pattern and Matches[i]. |
| DoesNotMatch v |
| }{ |
| { |
| Pattern: "ann:$:$", |
| DoesNotMatch: v{"", "ann", "bob", "ann:friend", "ann:friend:spouse"}, |
| }, |
| } |
| |
| for _, test := range invalid { |
| for i := 0; i < len(test.DoesNotMatch); i++ { |
| if remainder := g.match(nil, test.Pattern, convertToSet(test.DoesNotMatch[i])); len(remainder) != 0 || g.apprxs == nil { |
| t.Errorf("%q.match(%v) returned remainder %v, errs %v", test.Pattern, test.DoesNotMatch[i], remainder, g.apprxs) |
| } |
| } |
| |
| if len(test.DoesNotMatch) != 0 { |
| if remainder := g.match(nil, test.Pattern, convertToSet(test.DoesNotMatch...)); len(remainder) != 0 || g.apprxs == nil { |
| t.Errorf("%q.match(%v) returned remainder %v, errs %v", test.Pattern, test.DoesNotMatch, remainder, g.apprxs) |
| } |
| } |
| } |
| } |
| |
| // Test copied from v23/security/pattern_test.go. |
| func TestMatchCornerCases(t *testing.T) { |
| g := &grpClient{} |
| emptyBlessing := convertToSet("") |
| |
| if remainder := g.match(nil, security.AllPrincipals, emptyBlessing); remainder == nil || g.apprxs != nil { |
| t.Errorf("%q.match(%q) failed, errs %v", security.AllPrincipals, "", g.apprxs) |
| } |
| /*if remainder := match(security.NoExtension, "", nil); remainder != nil { |
| t.Errorf("%q.match(%q) returned remainder %v", security.NoExtension, "", remainder) |
| }*/ |
| if remainder := g.match(nil, security.BlessingPattern("ann:$"), emptyBlessing); len(remainder) != 0 || g.apprxs != nil { |
| t.Errorf("%q.match(%q) returned remainder %v, errs %v", "ann:$", "", remainder, g.apprxs) |
| } |
| if remainder := g.match(nil, security.BlessingPattern("ann"), emptyBlessing); len(remainder) != 0 || g.apprxs != nil { |
| t.Errorf("%q.match(%q) returned remainder %v, errs %v", "ann", "", remainder, g.apprxs) |
| } |
| } |
| |
| // TODO(hpucha): Add more tests for multiple groups in a pattern, |
| // nested groups, groups with cycles, inaccessible groups. |
| func TestMatchWithGrps(t *testing.T) { |
| var noMatch []string |
| exactMatch := []string{""} |
| |
| grpTests := []struct { |
| Pattern security.BlessingPattern |
| Blessing string |
| Groups groupClientRPCTest |
| Remainder []string |
| }{ |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d"}, |
| }, |
| Remainder: noMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"d:e:f", "c:d"}, |
| }, |
| Remainder: noMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c"}, |
| }, |
| Remainder: exactMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| }, |
| Remainder: exactMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c"}, |
| }, |
| Remainder: []string{"d:e"}, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d"}, |
| }, |
| Remainder: []string{"e"}, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| }, |
| Remainder: []string{"e", "d:e"}, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d:e", "c:d", "c"}, |
| }, |
| Remainder: []string{"", "e", "d:e"}, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:$", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| }, |
| Remainder: noMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:$", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d:e", "c:d", "c"}, |
| }, |
| Remainder: exactMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:e", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c"}, |
| }, |
| Remainder: noMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:e", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| }, |
| Remainder: exactMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:<grp:v/g2>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| "v/g2": []string{"e"}, |
| }, |
| Remainder: exactMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:<grp:v/g2>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| "v/g2": []string{"d:e"}, |
| }, |
| Remainder: exactMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:<grp:v/g2>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| "v/g2": []string{"d", "e"}, |
| }, |
| Remainder: []string{"", "e"}, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:<grp:v/g2>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| "v/g2": []string{"d:e", "d", "e"}, |
| }, |
| Remainder: []string{"", "e"}, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:<grp:v/g2>:e", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| "v/g2": []string{"d:e", "d", "e"}, |
| }, |
| Remainder: exactMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d:<grp:v/g2>"}, |
| "v/g2": []string{"e"}, |
| }, |
| Remainder: exactMatch, |
| }, |
| } |
| for _, test := range grpTests { |
| want := convertToSet(test.Remainder...) |
| g := &grpClient{rpcHandler: test.Groups} |
| got := g.match(nil, test.Pattern, convertToSet(test.Blessing)) |
| if g.apprxs != nil || !reflect.DeepEqual(got, want) { |
| t.Errorf("%q.match(%q) groupClientRPCTest %v got %v want %v, errs %v", test.Pattern, test.Blessing, test.Groups, got, want, g.apprxs) |
| } |
| } |
| } |
| |
| func TestMatchWithApproxGrps(t *testing.T) { |
| var noMatch []string |
| exactMatch := []string{""} |
| |
| grpTests := []struct { |
| Pattern security.BlessingPattern |
| Blessing string |
| Groups groupClientRPCTest |
| RemainderOver []string |
| RemainderUnder []string |
| }{ |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c", |
| Groups: groupClientRPCTest{}, |
| RemainderOver: exactMatch, |
| RemainderUnder: noMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{}, |
| RemainderOver: []string{"", "e", "d:e"}, |
| RemainderUnder: noMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:<grp:v/g2>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d", "c"}, |
| }, |
| RemainderOver: []string{"", "e"}, |
| RemainderUnder: noMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d:<grp:v/g2>"}, |
| }, |
| RemainderOver: exactMatch, |
| RemainderUnder: noMatch, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"c:d:<grp:v/g2>", "c"}, |
| }, |
| RemainderOver: []string{"", "d:e"}, |
| RemainderUnder: []string{"d:e"}, |
| }, |
| } |
| |
| for _, test := range grpTests { |
| want := convertToSet(test.RemainderOver...) |
| g := &grpClient{hint: ApproximationTypeOver, rpcHandler: test.Groups} |
| got := g.match(nil, test.Pattern, convertToSet(test.Blessing)) |
| if g.apprxs == nil || !reflect.DeepEqual(got, want) { |
| t.Errorf("%q.match(%q) groupClient {Over, %v} got %v want %v, errs %v", test.Pattern, test.Blessing, test.Groups, got, want, g.apprxs) |
| } |
| } |
| |
| for _, test := range grpTests { |
| want := convertToSet(test.RemainderUnder...) |
| g := &grpClient{hint: ApproximationTypeUnder, rpcHandler: test.Groups} |
| got := g.match(nil, test.Pattern, convertToSet(test.Blessing)) |
| if g.apprxs == nil || !reflect.DeepEqual(got, want) { |
| t.Errorf("%q.match(%q) groupClient {Under, %v} got %v want %v, errs %v", test.Pattern, test.Blessing, test.Groups, got, want, g.apprxs) |
| } |
| } |
| } |
| |
| func TestMatchWithCyclicGroups(t *testing.T) { |
| var noMatch []string |
| exactMatch := []string{""} |
| |
| grpTests := []struct { |
| Pattern security.BlessingPattern |
| Blessing string |
| Groups groupClientRPCTest |
| RemainderOver []string |
| RemainderUnder []string |
| lenErrors int |
| }{ |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"<grp:v/g1>"}, |
| }, |
| RemainderOver: exactMatch, |
| RemainderUnder: noMatch, |
| lenErrors: 1, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"<grp:v:g2>"}, |
| "v/g2": []string{"<grp:v:g1>"}, |
| }, |
| RemainderOver: []string{"", "e", "d:e"}, |
| RemainderUnder: noMatch, |
| lenErrors: 1, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:<grp:v/g3>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"<grp:v/g2>"}, |
| "v/g2": []string{"<grp:v/g1>"}, |
| "v/g3": []string{"d"}, |
| }, |
| RemainderOver: []string{"e"}, |
| RemainderUnder: noMatch, |
| lenErrors: 1, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>:<grp:v/g3>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"f:<grp:v/g2>"}, |
| "v/g2": []string{"<grp:v/g1>"}, |
| "v/g3": []string{"d"}, |
| }, |
| RemainderOver: noMatch, |
| RemainderUnder: noMatch, |
| lenErrors: 0, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"f:<grp:v/g2>", "<grp:v/g2>:<grp:v/g3>", "c"}, |
| "v/g2": []string{"<grp:v/g1>"}, |
| "v/g3": []string{"d"}, |
| }, |
| RemainderOver: []string{"e", "d:e"}, |
| RemainderUnder: []string{"d:e"}, |
| lenErrors: 1, |
| }, |
| { |
| Pattern: "a:b:<grp:v/g1>", |
| Blessing: "a:b:c:d:e", |
| Groups: groupClientRPCTest{ |
| "v/g1": []string{"<grp:v/g2>", "<grp:v/g1>"}, |
| "v/g2": []string{"<grp:v/g1>"}, |
| }, |
| RemainderOver: []string{"", "e", "d:e"}, |
| RemainderUnder: noMatch, |
| lenErrors: 2, |
| }, |
| } |
| |
| for _, test := range grpTests { |
| want := convertToSet(test.RemainderOver...) |
| g := &grpClient{hint: ApproximationTypeOver, rpcHandler: test.Groups} |
| got := g.match(nil, test.Pattern, convertToSet(test.Blessing)) |
| if len(g.apprxs) != test.lenErrors || !reflect.DeepEqual(got, want) { |
| t.Errorf("%q.match(%q) groupClientRPCTest %v got %v want %v, errs %v (got len %v, want len %v)", test.Pattern, test.Blessing, test.Groups, got, want, g.apprxs, len(g.apprxs), test.lenErrors) |
| } |
| } |
| |
| for _, test := range grpTests { |
| want := convertToSet(test.RemainderUnder...) |
| g := &grpClient{hint: ApproximationTypeUnder, rpcHandler: test.Groups} |
| got := g.match(nil, test.Pattern, convertToSet(test.Blessing)) |
| if len(g.apprxs) != test.lenErrors || !reflect.DeepEqual(got, want) { |
| t.Errorf("%q.match(%q) groupClientRPCTest %v got %v want %v, errs %v (got len %v, want len %v)", test.Pattern, test.Blessing, test.Groups, got, want, g.apprxs, len(g.apprxs), test.lenErrors) |
| } |
| } |
| } |
| |
| //////////////////// |
| // Helpers. |
| |
| type groupClientRPCTest map[string][]string |
| |
| func (c groupClientRPCTest) relate(ctx *context.T, group string, blessingChunks map[string]struct{}, hint ApproximationType, version string, vGrps map[string]struct{}) (map[string]struct{}, []Approximation, string, error) { |
| if _, ok := c[group]; !ok { |
| return nil, nil, "", errGrpNotFound |
| } |
| |
| var remainder = make(map[string]struct{}) |
| var approximations []Approximation |
| for _, p := range c[group] { |
| g := &grpClient{ |
| hint: hint, |
| version: version, |
| visited: vGrps, |
| rpcHandler: c, |
| } |
| |
| rem := g.match(ctx, security.BlessingPattern(p), blessingChunks) |
| remainder = union(remainder, rem) |
| approximations = append(approximations, g.apprxs...) |
| } |
| |
| return remainder, approximations, "", nil |
| } |
| |
| func contains(in map[string]struct{}, str string) bool { |
| _, ok := in[str] |
| return ok |
| } |