forked from tangled.org/core
this repo has no description

add server policies through the firehose

Changed files
+205 -159
api
appview
cmd
knotserver
knotserver
lexicons
rbac
+65 -75
api/tangled/cbor_gen.go
···
return nil
}
-
func (t *KnotPolicy) MarshalCBOR(w io.Writer) error {
+
func (t *KnotMember) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
cw := cbg.NewCborWriter(w)
+
fieldCount := 4
-
if _, err := cw.Write([]byte{165}); err != nil {
+
if t.AddedAt == nil {
+
fieldCount--
+
}
+
+
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
return err
}
···
return err
}
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.knot.policy"))); err != nil {
-
return err
-
}
-
if _, err := cw.WriteString(string("sh.tangled.knot.policy")); err != nil {
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.knot.member"))); err != nil {
return err
}
-
-
// t.Action (string) (string)
-
if len("action") > 1000000 {
-
return xerrors.Errorf("Value in field \"action\" was too long")
-
}
-
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("action"))); err != nil {
-
return err
-
}
-
if _, err := cw.WriteString(string("action")); err != nil {
-
return err
-
}
-
-
if len(t.Action) > 1000000 {
-
return xerrors.Errorf("Value in field t.Action was too long")
-
}
-
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Action))); err != nil {
-
return err
-
}
-
if _, err := cw.WriteString(string(t.Action)); err != nil {
+
if _, err := cw.WriteString(string("sh.tangled.knot.member")); err != nil {
return err
}
···
return err
}
-
// t.Object (string) (string)
-
if len("object") > 1000000 {
-
return xerrors.Errorf("Value in field \"object\" was too long")
+
// t.Member (string) (string)
+
if len("member") > 1000000 {
+
return xerrors.Errorf("Value in field \"member\" was too long")
}
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("object"))); err != nil {
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("member"))); err != nil {
return err
}
-
if _, err := cw.WriteString(string("object")); err != nil {
+
if _, err := cw.WriteString(string("member")); err != nil {
return err
}
-
if len(t.Object) > 1000000 {
-
return xerrors.Errorf("Value in field t.Object was too long")
+
if len(t.Member) > 1000000 {
+
return xerrors.Errorf("Value in field t.Member was too long")
}
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Object))); err != nil {
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Member))); err != nil {
return err
}
-
if _, err := cw.WriteString(string(t.Object)); err != nil {
+
if _, err := cw.WriteString(string(t.Member)); err != nil {
return err
}
-
// t.Subject (string) (string)
-
if len("subject") > 1000000 {
-
return xerrors.Errorf("Value in field \"subject\" was too long")
-
}
+
// t.AddedAt (string) (string)
+
if t.AddedAt != nil {
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
-
return err
-
}
-
if _, err := cw.WriteString(string("subject")); err != nil {
-
return err
-
}
+
if len("addedAt") > 1000000 {
+
return xerrors.Errorf("Value in field \"addedAt\" was too long")
+
}
-
if len(t.Subject) > 1000000 {
-
return xerrors.Errorf("Value in field t.Subject was too long")
-
}
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("addedAt"))); err != nil {
+
return err
+
}
+
if _, err := cw.WriteString(string("addedAt")); err != nil {
+
return err
+
}
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil {
-
return err
-
}
-
if _, err := cw.WriteString(string(t.Subject)); err != nil {
-
return err
+
if t.AddedAt == nil {
+
if _, err := cw.Write(cbg.CborNull); err != nil {
+
return err
+
}
+
} else {
+
if len(*t.AddedAt) > 1000000 {
+
return xerrors.Errorf("Value in field t.AddedAt was too long")
+
}
+
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.AddedAt))); err != nil {
+
return err
+
}
+
if _, err := cw.WriteString(string(*t.AddedAt)); err != nil {
+
return err
+
}
+
}
}
return nil
}
-
func (t *KnotPolicy) UnmarshalCBOR(r io.Reader) (err error) {
-
*t = KnotPolicy{}
+
func (t *KnotMember) UnmarshalCBOR(r io.Reader) (err error) {
+
*t = KnotMember{}
cr := cbg.NewCborReader(r)
···
}
if extra > cbg.MaxLength {
-
return fmt.Errorf("KnotPolicy: map struct too large (%d)", extra)
+
return fmt.Errorf("KnotMember: map struct too large (%d)", extra)
}
n := extra
···
t.LexiconTypeID = string(sval)
}
-
// t.Action (string) (string)
-
case "action":
-
-
{
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
-
if err != nil {
-
return err
-
}
-
-
t.Action = string(sval)
-
}
// t.Domain (string) (string)
case "domain":
···
t.Domain = string(sval)
}
-
// t.Object (string) (string)
-
case "object":
+
// t.Member (string) (string)
+
case "member":
{
sval, err := cbg.ReadStringWithMax(cr, 1000000)
···
return err
}
-
t.Object = string(sval)
+
t.Member = string(sval)
}
-
// t.Subject (string) (string)
-
case "subject":
+
// t.AddedAt (string) (string)
+
case "addedAt":
{
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
+
b, err := cr.ReadByte()
if err != nil {
return err
}
+
if b != cbg.CborNull[0] {
+
if err := cr.UnreadByte(); err != nil {
+
return err
+
}
-
t.Subject = string(sval)
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
+
if err != nil {
+
return err
+
}
+
+
t.AddedAt = (*string)(&sval)
+
}
}
default:
+25
api/tangled/knotmember.go
···
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
+
+
package tangled
+
+
// schema: sh.tangled.knot.member
+
+
import (
+
"github.com/bluesky-social/indigo/lex/util"
+
)
+
+
const (
+
KnotMemberNSID = "sh.tangled.knot.member"
+
)
+
+
func init() {
+
util.RegisterType("sh.tangled.knot.member", &KnotMember{})
+
} //
+
// RECORDTYPE: KnotMember
+
type KnotMember struct {
+
LexiconTypeID string `json:"$type,const=sh.tangled.knot.member" cborgen:"$type,const=sh.tangled.knot.member"`
+
AddedAt *string `json:"addedAt,omitempty" cborgen:"addedAt,omitempty"`
+
// domain: domain that this member now belongs to
+
Domain string `json:"domain" cborgen:"domain"`
+
Member string `json:"member" cborgen:"member"`
+
}
-29
api/tangled/knotpolicy.go
···
-
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
-
-
package tangled
-
-
// schema: sh.tangled.knot.policy
-
-
import (
-
"github.com/bluesky-social/indigo/lex/util"
-
)
-
-
const (
-
KnotPolicyNSID = "sh.tangled.knot.policy"
-
)
-
-
func init() {
-
util.RegisterType("sh.tangled.knot.policy", &KnotPolicy{})
-
} //
-
// RECORDTYPE: KnotPolicy
-
type KnotPolicy struct {
-
LexiconTypeID string `json:"$type,const=sh.tangled.knot.policy" cborgen:"$type,const=sh.tangled.knot.policy"`
-
// action: action associated with the key
-
Action string `json:"action" cborgen:"action"`
-
// domain: domain of the key
-
Domain string `json:"domain" cborgen:"domain"`
-
// object: object associated with the key
-
Object string `json:"object" cborgen:"object"`
-
// subject: subject of the key
-
Subject string `json:"subject" cborgen:"subject"`
-
}
+1
appview/consts.go
···
SessionExpiry = "expiry"
SessionAuthenticated = "authenticated"
TimeLayout = "2006-01-02 15:04:05.999999999 -0700 MST"
+
SqliteDbPath = "appview.db"
)
+3 -4
appview/state/rbac.go rbac/rbac.go
···
-
package state
+
package rbac
import (
"database/sql"
···
return matched
}
-
func NewEnforcer() (*Enforcer, error) {
+
func NewEnforcer(path string) (*Enforcer, error) {
m, err := model.NewModelFromString(Model)
if err != nil {
return nil, err
}
-
// TODO: conf this
-
db, err := sql.Open("sqlite3", "appview.db")
+
db, err := sql.Open("sqlite3", path)
if err != nil {
return nil, err
}
+35 -11
appview/state/state.go
···
"github.com/sotangled/tangled/appview/auth"
"github.com/sotangled/tangled/appview/db"
"github.com/sotangled/tangled/appview/pages"
+
"github.com/sotangled/tangled/rbac"
)
type State struct {
db *db.DB
auth *auth.Auth
-
enforcer *Enforcer
+
enforcer *rbac.Enforcer
}
func Make() (*State, error) {
-
db, err := db.Make("appview.db")
+
+
db, err := db.Make(appview.SqliteDbPath)
if err != nil {
return nil, err
}
···
return nil, err
}
-
enforcer, err := NewEnforcer()
+
enforcer, err := rbac.NewEnforcer(appview.SqliteDbPath)
if err != nil {
return nil, err
}
···
Collection: tangled.PublicKeyNSID,
Repo: did,
Rkey: uuid.New().String(),
-
Record: &lexutil.LexiconTypeDecoder{Val: &tangled.PublicKey{
-
Created: time.Now().String(),
-
Key: key,
-
Name: name,
-
}},
+
Record: &lexutil.LexiconTypeDecoder{
+
Val: &tangled.PublicKey{
+
Created: time.Now().Format(time.RFC3339),
+
Key: key,
+
Name: name,
+
}},
})
-
// invalid record
if err != nil {
log.Printf("failed to create record: %s", err)
···
w.Write([]byte("failed to resolve member did to a handle"))
return
}
-
log.Printf("adding %s to %s\n", memberIdent.Handle.String(), domain)
+
// announce this relation into the firehose, store into owners' pds
+
client, _ := s.auth.AuthorizedClient(r)
+
currentUser := s.auth.GetUser(r)
+
addedAt := time.Now().Format(time.RFC3339)
+
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
+
Collection: tangled.KnotMemberNSID,
+
Repo: currentUser.Did,
+
Rkey: uuid.New().String(),
+
Record: &lexutil.LexiconTypeDecoder{
+
Val: &tangled.KnotMember{
+
Member: memberIdent.DID.String(),
+
Domain: domain,
+
AddedAt: &addedAt,
+
}},
+
})
+
// invalid record
+
if err != nil {
+
log.Printf("failed to create record: %s", err)
+
return
+
}
+
+
log.Println("created atproto record: ", resp.Uri)
+
err = s.enforcer.AddMember(domain, memberIdent.DID.String())
if err != nil {
w.Write([]byte(fmt.Sprint("failed to add member: ", err)))
···
r.Post("/key", s.RegistrationKey)
r.Route("/{domain}", func(r chi.Router) {
-
r.Get("/", s.KnotServerInfo)
r.Post("/init", s.InitKnotServer)
+
r.Get("/", s.KnotServerInfo)
r.Route("/member", func(r chi.Router) {
r.Use(RoleMiddleware(s, "server:owner"))
r.Get("/", s.ListMembers)
+1 -1
cmd/gen.go
···
"api/tangled/cbor_gen.go",
"tangled",
shtangled.PublicKey{},
-
shtangled.KnotPolicy{},
+
shtangled.KnotMember{},
); err != nil {
panic(err)
}
+7 -1
cmd/knotserver/main.go
···
"github.com/sotangled/tangled/knotserver"
"github.com/sotangled/tangled/knotserver/config"
"github.com/sotangled/tangled/knotserver/db"
+
"github.com/sotangled/tangled/rbac"
)
func main() {
···
log.Fatalf("failed to setup db: %s", err)
}
-
mux, err := knotserver.Setup(ctx, c, db)
+
e, err := rbac.NewEnforcer(c.Server.DBPath)
+
if err != nil {
+
log.Fatalf("failed to setup rbac enforcer: %s", err)
+
}
+
+
mux, err := knotserver.Setup(ctx, c, db, e)
if err != nil {
log.Fatal(err)
}
+26 -4
knotserver/handler.go
···
"github.com/sotangled/tangled/knotserver/config"
"github.com/sotangled/tangled/knotserver/db"
"github.com/sotangled/tangled/knotserver/jsclient"
+
"github.com/sotangled/tangled/rbac"
+
)
+
+
const (
+
ThisServer = "thisserver" // resource identifier for rbac enforcement
)
type Handle struct {
c *config.Config
db *db.DB
js *jsclient.JetstreamClient
+
e *rbac.Enforcer
// init is a channel that is closed when the knot has been initailized
// i.e. when the first user (knot owner) has been added.
···
knotInitialized bool
}
-
func Setup(ctx context.Context, c *config.Config, db *db.DB) (http.Handler, error) {
+
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer) (http.Handler, error) {
r := chi.NewRouter()
h := Handle{
c: c,
db: db,
+
e: e,
init: make(chan struct{}),
}
-
err := h.StartJetstream(ctx)
+
err := e.AddDomain(ThisServer)
+
if err != nil {
+
return nil, fmt.Errorf("failed to setup enforcer: %w", err)
+
}
+
+
err = h.StartJetstream(ctx)
if err != nil {
return nil, fmt.Errorf("failed to start jetstream: %w", err)
}
···
}
func (h *Handle) StartJetstream(ctx context.Context) error {
-
colections := []string{tangled.PublicKeyNSID}
+
collections := []string{tangled.PublicKeyNSID, tangled.KnotMemberNSID}
dids := []string{}
-
h.js = jsclient.NewJetstreamClient(colections, dids)
+
h.js = jsclient.NewJetstreamClient(collections, dids)
messages, err := h.js.ReadJetstream(ctx)
if err != nil {
return fmt.Errorf("failed to read from jetstream: %w", err)
···
log.Printf("failed to add public key: %v", err)
} else {
log.Printf("added public key from firehose: %s", data["did"])
+
}
+
case tangled.KnotMemberNSID:
+
did := data["did"].(string)
+
record := commit["record"].(map[string]interface{})
+
ok, err := h.e.E.Enforce(did, ThisServer, ThisServer, "server:invite")
+
if err != nil || !ok {
+
log.Printf("failed to add member from did %s", did)
+
} else {
+
log.Printf("adding member")
+
h.e.AddMember(ThisServer, record["member"].(string))
}
default:
}
+8
knotserver/jsclient/jetstream.go
···
j.triggerReconnect()
}
+
// Adds one did to the did list
+
func (j *JetstreamClient) AddDid(did string) {
+
j.mu.Lock()
+
j.dids = append(j.dids, did)
+
j.mu.Unlock()
+
j.triggerReconnect()
+
}
+
func (j *JetstreamClient) triggerReconnect() {
select {
case j.reconnectCh <- struct{}{}:
+1
knotserver/routes.go
···
}
h.js.UpdateDids([]string{data.Did})
+
h.e.AddOwner(ThisServer, data.Did)
// Signal that the knot is ready
close(h.init)
+33
lexicons/member.json
···
+
{
+
"lexicon": 1,
+
"id": "sh.tangled.knot.member",
+
"needsCbor": true,
+
"needsType": true,
+
"defs": {
+
"main": {
+
"type": "record",
+
"key": "tid",
+
"record": {
+
"type": "object",
+
"required": [
+
"member",
+
"domain"
+
],
+
"properties": {
+
"member": {
+
"type": "string",
+
"format": "did"
+
},
+
"domain": {
+
"type": "string",
+
"description": "domain that this member now belongs to"
+
},
+
"addedAt": {
+
"type": "string",
+
"format": "datetime"
+
}
+
}
+
}
+
}
+
}
+
}
-34
lexicons/policy.json
···
-
{
-
"lexicon": 1,
-
"id": "sh.tangled.knot.policy",
-
"needsCbor": true,
-
"needsType": true,
-
"defs": {
-
"main": {
-
"type": "record",
-
"key": "tid",
-
"record": {
-
"type": "object",
-
"required": ["subject", "domain", "object", "action"],
-
"properties": {
-
"subject": {
-
"type": "string",
-
"description": "subject of the key"
-
},
-
"domain": {
-
"type": "string",
-
"description": "domain of the key"
-
},
-
"object": {
-
"type": "string",
-
"description": "object associated with the key"
-
},
-
"action": {
-
"type": "string",
-
"description": "action associated with the key"
-
}
-
}
-
}
-
}
-
}
-
}