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 "amadan.net/rastrillo/rastrillo/sessions"
5 "context"
6 "crypto/sha256"
7 "database/sql"
8 "encoding/base64"
9 "errors"
10 "net/http"
11
12 "amadan.net/oficina/home/handoff/seal"
13 "amadan.net/oficina/home/roster"
14 "amadan.net/rastrillo/rastrillo/migrate"
15 "github.com/carlosframework/docs/internal/docs/principalstore"
16 "github.com/carlosframework/docs/internal/docs/teamstate"
17 "github.com/go-json-experiment/json"
18)
19
20// TeamConfig is explicit deployment configuration, never browser request input.
21type TeamConfig struct {
22 transport http.RoundTripper
23 clock roster.Clock
24 TeamID string
25 DirectoryOrigin string
26 ProvisioningBase64 string
27 OperatorKeysBase64 string
28 RosterToken string
29}
30
31var errTeamConfig = errors.New("docs: invalid team configuration")
32var errTeamUnavailable = errors.New("docs: team authority unavailable")
33
34type teamSettings struct {
35 transport http.RoundTripper
36 clock roster.Clock
37 record roster.VerifiedRecord
38 token string
39 fingerprint string
40}
41
42var TeamBootSchema = migrate.Merge(BootSchema, principalstore.Schema, teamstate.Schema)
43
44func validatedTeamConfig(cfg *TeamConfig, home *homeSettings, origin string) (*teamSettings, error) {
45 if cfg == nil {
46 return nil, nil
47 }
48 if home == nil || !roster.ValidChallenge(cfg.RosterToken) {
49 return nil, errTeamConfig
50 }
51 raw, ok := homeConfigBase64(cfg.ProvisioningBase64, roster.BodyLimit)
52 if !ok {
53 return nil, errTeamConfig
54 }
55 encoded, ok := homeConfigBase64(cfg.OperatorKeysBase64, 4096)
56 if !ok {
57 return nil, errTeamConfig
58 }
59 var keys []roster.Key
60 if json.Unmarshal(encoded, &keys, json.RejectUnknownMembers(true)) != nil {
61 return nil, errTeamConfig
62 }
63 expected := roster.Bindings{Issuer: home.issuer, Directory: cfg.DirectoryOrigin, Consumer: home.consumerID, App: "docs", Team: cfg.TeamID, Audience: origin}
64 record, e := roster.VerifyProvisioning(raw, keys, expected)
65 if e != nil {
66 return nil, errTeamConfig
67 }
68 b, e := json.Marshal(record.Bindings())
69 if e != nil {
70 return nil, errTeamConfig
71 }
72 digest := sha256.Sum256(b)
73 return &teamSettings{transport: cfg.transport, clock: cfg.clock, record: record, token: cfg.RosterToken, fingerprint: base64.RawURLEncoding.EncodeToString(digest[:])}, nil
74}
75
76type teamRuntime struct {
77 bridge *homeBridge
78 readSession func(context.Context, *http.Request) (sessions.Session, bool)
79 cache *roster.Cache
80 principals *principalstore.Store
81 bindings roster.Bindings
82 db *sql.DB
83}
84
85func newTeamRuntime(ctx context.Context, settings *teamSettings, db *sql.DB, home *homeSettings) (*teamRuntime, error) {
86 if settings == nil || db == nil || home == nil {
87 return nil, errTeamConfig
88 }
89 b := settings.record.Bindings()
90 var foreign int
91 if e := db.QueryRowContext(ctx, "SELECT (SELECT COUNT(*) FROM docs_identity_links WHERE issuer!=?)+(SELECT COUNT(*) FROM docs_home_identities WHERE issuer!=?)", b.Issuer, b.Issuer).Scan(&foreign); e != nil {
92 return nil, errTeamUnavailable
93 }
94 if foreign != 0 {
95 return nil, errTeamConfig
96 }
97 if e := teamstate.Activate(ctx, db, settings.fingerprint); e != nil {
98 return nil, errTeamConfig
99 }
100 sealer, e := seal.New(home.dek, "oficina/docs")
101 if e != nil {
102 return nil, errTeamConfig
103 }
104 cache, e := roster.New(ctx, roster.Config{Record: settings.record, Token: settings.token, Transport: settings.transport, Clock: settings.clock, Durable: teamstate.Evidence{DB: db}, Sealer: sealer})
105 if e != nil {
106 return nil, errTeamUnavailable
107 }
108 runtime := &teamRuntime{cache: cache, bindings: b, db: db}
109 principals, e := principalstore.New(db, principalstore.Config{Issuer: b.Issuer, Team: b.Team, RequireMembership: func(ctx context.Context, request principalstore.MembershipRequest) error {
110 d := cache.Check(ctx, request.Issuer, request.Team, request.Subject)
111 if d.State == roster.Member && d.Lease.Valid() {
112 return nil
113 }
114 if d.State == roster.NotMember {
115 return principalstore.ErrRefused
116 }
117 return principalstore.ErrUnavailable
118 }})
119 if e != nil {
120 return nil, errTeamUnavailable
121 }
122 runtime.principals = principals
123 return runtime, nil
124}
125