Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

1package xrpc 2 3import ( 4 "context" 5 "database/sql" 6 "log/slog" 7 "net/http" 8 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 "tangled.org/core/knotserver/db" 11 "tangled.org/core/knotserver/keys" 12 xrpcerr "tangled.org/core/xrpc/errors" 13) 14 15type aclGrant struct { 16 role string 17 subject syntax.DID 18 inAcl func() (bool, error) 19 inTable func() (bool, error) 20 insertRow func(*sql.Tx) error 21 deleteRow func() error 22 grantAcl func() error 23 emit func() error 24} 25 26type aclRevoke struct { 27 role string 28 subject syntax.DID 29 inAcl func() (bool, error) 30 inTable func() (bool, error) 31 removeAcl func() (bool, error) 32 restoreAcl func() error 33 deleteRow func(*sql.Tx) error 34 emit func() error 35} 36 37func (h *Xrpc) applyAclGrant(ctx context.Context, l *slog.Logger, g aclGrant) (int, *xrpcerr.XrpcError) { 38 fail := func(status int, e xrpcerr.XrpcError) (int, *xrpcerr.XrpcError) { 39 return status, &e 40 } 41 42 inAcl, err := g.inAcl() 43 if err != nil { 44 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 45 } 46 inTable, err := g.inTable() 47 if err != nil { 48 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 49 } 50 if inAcl && inTable { 51 l.Info("subject already granted, no-op", "role", g.role, "subject", g.subject) 52 return http.StatusOK, nil 53 } 54 55 didKnown, err := db.IsDidKnown(h.Db, g.subject.String()) 56 if err != nil { 57 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 58 } 59 60 tx, err := h.Db.BeginTx(ctx, nil) 61 if err != nil { 62 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 63 } 64 committed := false 65 defer func() { 66 if !committed { 67 tx.Rollback() 68 } 69 }() 70 71 if err := db.AddDid(tx, g.subject.String()); err != nil { 72 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 73 } 74 if err := g.insertRow(tx); err != nil { 75 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 76 } 77 if err := tx.Commit(); err != nil { 78 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 79 } 80 committed = true 81 82 if err := g.grantAcl(); err != nil { 83 if !inTable { 84 if rbErr := g.deleteRow(); rbErr != nil { 85 l.Error("failed to roll back row after ACL grant failed", "role", g.role, "subject", g.subject, "error", rbErr) 86 } 87 } 88 if !didKnown { 89 if rbErr := db.RemoveDid(h.Db, g.subject.String()); rbErr != nil { 90 l.Error("failed to roll back known_did after ACL grant failed", "role", g.role, "subject", g.subject, "error", rbErr) 91 } 92 } 93 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 94 } 95 96 h.Ingester.AddDid(g.subject.String()) 97 h.fetchKeysAsync(ctx, l, g.subject) 98 99 if g.emit != nil { 100 if err := g.emit(); err != nil { 101 l.Error("failed to emit acl grant event, appview reconcile will catch up", "role", g.role, "subject", g.subject, "error", err) 102 } 103 } 104 105 l.Info("granted", "role", g.role, "subject", g.subject) 106 return http.StatusOK, nil 107} 108 109func (h *Xrpc) applyAclRevoke(ctx context.Context, l *slog.Logger, rv aclRevoke) (int, *xrpcerr.XrpcError) { 110 fail := func(status int, e xrpcerr.XrpcError) (int, *xrpcerr.XrpcError) { 111 return status, &e 112 } 113 114 inAcl, err := rv.inAcl() 115 if err != nil { 116 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 117 } 118 inTable, err := rv.inTable() 119 if err != nil { 120 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 121 } 122 if !inAcl && !inTable { 123 l.Info("subject not granted, no-op", "role", rv.role, "subject", rv.subject) 124 return http.StatusOK, nil 125 } 126 127 removed := false 128 if inAcl { 129 removed, err = rv.removeAcl() 130 if err != nil { 131 return fail(http.StatusInternalServerError, xrpcerr.GenericError(err)) 132 } 133 } 134 135 failRestoringACL := func(status int, e xrpcerr.XrpcError) (int, *xrpcerr.XrpcError) { 136 if removed { 137 if rbErr := rv.restoreAcl(); rbErr != nil { 138 l.Error("failed to restore ACL after remove rollback", "role", rv.role, "subject", rv.subject, "error", rbErr) 139 } 140 } 141 return fail(status, e) 142 } 143 144 stillKnown, err := h.Enforcer.HasAnyPolicyForUser(rv.subject.String()) 145 if err != nil { 146 return failRestoringACL(http.StatusInternalServerError, xrpcerr.GenericError(err)) 147 } 148 149 tx, err := h.Db.BeginTx(ctx, nil) 150 if err != nil { 151 return failRestoringACL(http.StatusInternalServerError, xrpcerr.GenericError(err)) 152 } 153 committed := false 154 defer func() { 155 if !committed { 156 tx.Rollback() 157 } 158 }() 159 160 if err := rv.deleteRow(tx); err != nil { 161 return failRestoringACL(http.StatusInternalServerError, xrpcerr.GenericError(err)) 162 } 163 if !stillKnown { 164 if err := db.RemoveDid(tx, rv.subject.String()); err != nil { 165 return failRestoringACL(http.StatusInternalServerError, xrpcerr.GenericError(err)) 166 } 167 } 168 if err := tx.Commit(); err != nil { 169 return failRestoringACL(http.StatusInternalServerError, xrpcerr.GenericError(err)) 170 } 171 committed = true 172 173 if !stillKnown { 174 h.Ingester.RemoveDid(rv.subject.String()) 175 } 176 177 if rv.emit != nil { 178 if err := rv.emit(); err != nil { 179 l.Error("failed to emit acl revoke event, appview reconcile will catch up", "role", rv.role, "subject", rv.subject, "error", err) 180 } 181 } 182 183 l.Info("revoked", "role", rv.role, "subject", rv.subject, "did_dropped", !stillKnown) 184 return http.StatusOK, nil 185} 186 187func (h *Xrpc) fetchKeysAsync(ctx context.Context, l *slog.Logger, subject syntax.DID) { 188 kctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), keyFetchTimeout) 189 go func() { 190 defer cancel() 191 if err := keys.FetchAndStore(kctx, h.Resolver.Directory(), h.Db, subject); err != nil { 192 l.Warn("failed to fetch subject public keys, continuing", "subject", subject, "error", err) 193 } 194 }() 195}