oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1package docs
2
3import (
4 "bytes"
5 "context"
6 "encoding/base64"
7 "errors"
8 "strings"
9 "testing"
10
11 "amadan.net/oficina/home/roster"
12 family "amadan.net/rastrillo/rastrillo/crypto"
13 "amadan.net/rastrillo/rastrillo/migrate"
14 "github.com/carlosframework/docs/internal/docs/principalstore"
15 "github.com/carlosframework/docs/internal/docs/teamstate"
16 "github.com/go-json-experiment/json"
17 "gorm.io/gorm"
18)
19
20func teamConfigFixture(t *testing.T) (*TeamConfig, *homeSettings) {
21 t.Helper()
22 op, e := family.Generate()
23 if e != nil {
24 t.Fatal(e)
25 }
26 key, e := family.Generate()
27 if e != nil {
28 t.Fatal(e)
29 }
30 b := roster.Bindings{Issuer: "https://id.example", Directory: "https://home.example", Consumer: "docs-A", App: "docs", Team: "TeamA", Audience: "https://docs.example"}
31 p := roster.Provisioning{Version: 1, Operation: "provision1", Sequence: 1, Bindings: b, Generation: base64.RawURLEncoding.EncodeToString(bytes.Repeat([]byte{1}, 32)), MinEpoch: 1, Keys: []roster.Key{{ID: "roster1", Public: base64.RawURLEncoding.EncodeToString(key.SignPub())}}}
32 raw, e := roster.SignProvisioning(op, "operator1", p)
33 if e != nil {
34 t.Fatal(e)
35 }
36 keys, _ := json.Marshal([]roster.Key{{ID: "operator1", Public: base64.RawURLEncoding.EncodeToString(op.SignPub())}})
37 return &TeamConfig{TeamID: b.Team, DirectoryOrigin: b.Directory, ProvisioningBase64: base64.StdEncoding.EncodeToString(raw), OperatorKeysBase64: base64.StdEncoding.EncodeToString(keys), RosterToken: base64.RawURLEncoding.EncodeToString(bytes.Repeat([]byte{7}, 32))}, &homeSettings{issuer: b.Issuer, consumerID: b.Consumer, dek: bytes.Repeat([]byte{9}, 32)}
38}
39func TestTeamConfigPinsAndColdMarker(t *testing.T) {
40 cfg, home := teamConfigFixture(t)
41 settings, e := validatedTeamConfig(cfg, home, "https://docs.example")
42 if e != nil {
43 t.Fatal(e)
44 }
45 for _, change := range []func(*TeamConfig){func(c *TeamConfig) { c.TeamID = "Other" }, func(c *TeamConfig) { c.DirectoryOrigin = "https://other.example" }, func(c *TeamConfig) { c.RosterToken += "=" }, func(c *TeamConfig) { c.ProvisioningBase64 += "\n" }, func(c *TeamConfig) { c.OperatorKeysBase64 = "e30=" }} {
46 bad := *cfg
47 change(&bad)
48 if _, e := validatedTeamConfig(&bad, home, "https://docs.example"); e != errTeamConfig {
49 t.Fatal("bad team config")
50 }
51 }
52 if _, e := validatedTeamConfig(cfg, nil, "https://docs.example"); e != errTeamConfig {
53 t.Fatal("team without Home")
54 }
55 app := newSampleDocumentsInternalTestApp(t, "0")
56 if _, e := migrate.Apply(t.Context(), app.db, TeamBootSchema); e != nil {
57 t.Fatal(e)
58 }
59 rt, e := newTeamRuntime(t.Context(), settings, app.db.Writer(), home)
60 if e != nil {
61 t.Fatal(e)
62 }
63 if rt.cache == nil || rt.principals == nil {
64 t.Fatal("runtime missing")
65 }
66 if got, yes, e := teamstate.Required(t.Context(), app.db.Writer()); e != nil || !yes || got != settings.fingerprint {
67 t.Fatal("runtime did not commit marker")
68 }
69 var count int
70 _ = app.db.Writer().QueryRow("SELECT COUNT(*) FROM docs_home_identities").Scan(&count)
71 if count != 0 {
72 t.Fatal("runtime seeded identity")
73 }
74}
75func TestTeamIdentityAddressAbsenceNeverRedeemsEmptyGrant(t *testing.T) {
76 app := newSampleDocumentsInternalTestApp(t, "0")
77 ctx := context.Background()
78 s := app.Store()
79 local, e := s.EnsurePerson(ctx, "owner@example.com")
80 if e != nil {
81 t.Fatal(e)
82 }
83 doc, e := s.Create(ctx, local, "private")
84 if e != nil {
85 t.Fatal(e)
86 }
87 if _, e = migrate.Apply(ctx, app.db, TeamBootSchema); e != nil {
88 t.Fatal(e)
89 }
90 ps, e := principalstore.New(app.db.Writer(), principalstore.Config{Issuer: "https://id.example", Team: "TeamA", RequireMembership: func(context.Context, principalstore.MembershipRequest) error { return nil }})
91 if e != nil {
92 t.Fatal(e)
93 }
94 subject := "pr_" + strings.Repeat("a", 26)
95 p, e := ps.EnsureHome(ctx, principalstore.HomeIdentity{Issuer: "https://id.example", Subject: subject})
96 if e != nil {
97 t.Fatal(e)
98 }
99 rt, _, _ := testTeamRoster(t, s, app)
100 rt.principals = ps
101 s.team = rt
102 resolver := identityResolver{store: s, team: rt}
103 id, e := resolver.resolve(identityRequest("GET", subject, homeAssertionMethod))
104 if e != nil || id.Actor.PersonID != p.ID || id.Actor.VerifiedAddress != nil || id.HomeSubject != subject {
105 t.Fatal("wrong Home principal", e)
106 }
107 ctx = context.WithValue(ctx, accessContextKey{}, rt.resolveAccess(ctx, id))
108 if e = s.db.Create(&Grant{DocID: doc.ID, Subject: "", Role: RoleWriter, CreatedBy: local}).Error; e != nil {
109 t.Fatal(e)
110 }
111 if _, e = s.GetFor(ctx, id.Actor, doc.Sqid); !errors.Is(e, gorm.ErrRecordNotFound) {
112 t.Fatal("nil address redeemed malformed grant", e)
113 }
114 if s.CanWrite(ctx, id.Actor, doc.ID) {
115 t.Fatal("nil address redeemed malformed writer grant")
116 }
117 own, e := s.Create(ctx, p.ID, "Home owned")
118 if e != nil {
119 t.Fatal(e)
120 }
121 if e = s.Grant(ctx, p.ID, own.Sqid, "guest@example.com", RoleReader); e != nil {
122 t.Fatal("addressless owner could not grant", e)
123 }
124 var people int64
125 s.db.Model(&Person{}).Count(&people)
126 if people != 1 {
127 t.Fatal("Home identity created legacy person")
128 }
129 if _, e = resolver.resolve(identityRequest("GET", "pr_"+strings.Repeat("b", 26), homeAssertionMethod)); e != errIdentityRefused {
130 t.Fatal("unknown identity allocated")
131 }
132 if _, e = resolver.resolve(identityRequest("GET", "unknown@example.com", "unsupported")); e != errIdentityRefused {
133 t.Fatal("unsupported local method accepted")
134 }
135}
136