mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-06 05:19:31 -06:00
wahhh getting there
This commit is contained in:
parent
41e6e8ed10
commit
d4c919d273
12 changed files with 214 additions and 151 deletions
|
|
@ -23,7 +23,6 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsersGETHandler should be served at https://example.org/users/:username.
|
// UsersGETHandler should be served at https://example.org/users/:username.
|
||||||
|
|
@ -55,60 +54,14 @@ func (m *Module) UsersGETHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
l.Tracef("negotiated format: %s", format)
|
||||||
|
|
||||||
// get the account the request is referring to
|
// make a copy of the context to pass along so we don't break anything
|
||||||
requestedAccount := >smodel.Account{}
|
cp := c.Copy()
|
||||||
if err := m.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
user, err := m.processor.GetAPUser(requestedUsername, cp.Request) // GetAPUser handles auth as well
|
||||||
l.Errorf("database error getting account with username %s: %s", requestedUsername, err)
|
|
||||||
// we'll just return not authorized here to avoid giving anything away
|
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// and create a transport for it
|
|
||||||
transport, err := m.federator.TransportController().NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("error creating transport for username %s: %s", requestedUsername, err)
|
l.Info(err.Error())
|
||||||
// we'll just return not authorized here to avoid giving anything away
|
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// authenticate the request
|
c.JSON(http.StatusOK, user)
|
||||||
authentication, err := federation.AuthenticateFederatedRequest(transport, c.Request)
|
|
||||||
if err != nil {
|
|
||||||
l.Errorf("error authenticating GET user request: %s", err)
|
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !authentication.Authenticated {
|
|
||||||
l.Debug("request not authorized")
|
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
requestingAccount := >smodel.Account{}
|
|
||||||
if authentication.RequestingPublicKeyID != nil {
|
|
||||||
if err := m.db.GetWhere("public_key_uri", authentication.RequestingPublicKeyID.String(), requestingAccount); err != nil {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
authorization, err := federation.AuthorizeFederatedRequest
|
|
||||||
|
|
||||||
person, err := m.tc.AccountToAS(requestedAccount)
|
|
||||||
if err != nil {
|
|
||||||
l.Errorf("error converting account to ap person: %s", err)
|
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := person.Serialize()
|
|
||||||
if err != nil {
|
|
||||||
l.Errorf("error serializing user: %s", err)
|
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, data)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,30 @@ func (f *federatingDB) Unlock(c context.Context, id *url.URL) error {
|
||||||
//
|
//
|
||||||
// The library makes this call only after acquiring a lock first.
|
// The library makes this call only after acquiring a lock first.
|
||||||
func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (contains bool, err error) {
|
func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (contains bool, err error) {
|
||||||
return false, nil
|
|
||||||
|
if !util.IsInboxPath(inbox) {
|
||||||
|
return false, fmt.Errorf("%s is not an inbox URI", inbox.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !util.IsStatusesPath(id) {
|
||||||
|
return false, fmt.Errorf("%s is not a status URI", id.String())
|
||||||
|
}
|
||||||
|
_, statusID, err := util.ParseStatusesPath(inbox)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("status URI %s was not parseable: %s", id.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.db.GetByID(statusID, >smodel.Status{}); err != nil {
|
||||||
|
if _, ok := err.(ErrNoEntries); ok {
|
||||||
|
// we don't have it
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
// actual error
|
||||||
|
return false, fmt.Errorf("error getting status from db: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we must have it
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInbox returns the first ordered collection page of the outbox at
|
// GetInbox returns the first ordered collection page of the outbox at
|
||||||
|
|
@ -128,11 +151,6 @@ func (f *federatingDB) SetInbox(c context.Context, inbox vocab.ActivityStreamsOr
|
||||||
// the database has an entry for the IRI.
|
// the database has an entry for the IRI.
|
||||||
// The library makes this call only after acquiring a lock first.
|
// The library makes this call only after acquiring a lock first.
|
||||||
func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
||||||
l := f.log.WithFields(logrus.Fields{
|
|
||||||
"func": "Owns",
|
|
||||||
"activityID": id.String(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// if the id host isn't this instance host, we don't own this IRI
|
// if the id host isn't this instance host, we don't own this IRI
|
||||||
if id.Host != f.config.Host {
|
if id.Host != f.config.Host {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
@ -142,27 +160,18 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
||||||
|
|
||||||
// check if it's a status, eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS
|
// check if it's a status, eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS
|
||||||
if util.IsStatusesPath(id) {
|
if util.IsStatusesPath(id) {
|
||||||
username, uid, err := util.ParseStatusesPath(id)
|
_, uid, err := util.ParseStatusesPath(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
||||||
}
|
}
|
||||||
acct := >smodel.Account{}
|
if err := f.db.GetWhere("uri", uid, >smodel.Status{}); err != nil {
|
||||||
if err := f.db.GetLocalAccountByUsername(username, acct); err != nil {
|
|
||||||
if _, ok := err.(ErrNoEntries); ok {
|
|
||||||
// there are no entries for this username
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
|
|
||||||
}
|
|
||||||
status := >smodel.Status{}
|
|
||||||
if err := f.db.GetByID(uid, status); err != nil {
|
|
||||||
if _, ok := err.(ErrNoEntries); ok {
|
if _, ok := err.(ErrNoEntries); ok {
|
||||||
// there are no entries for this status
|
// there are no entries for this status
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
// an actual error happened
|
||||||
return false, fmt.Errorf("database error fetching status with id %s: %s", uid, err)
|
return false, fmt.Errorf("database error fetching status with id %s: %s", uid, err)
|
||||||
}
|
}
|
||||||
// the user exists, the status exists, we own both, we're good
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,34 +181,52 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
||||||
}
|
}
|
||||||
acct := >smodel.Account{}
|
if err := f.db.GetLocalAccountByUsername(username, >smodel.Account{}); err != nil {
|
||||||
if err := f.db.GetLocalAccountByUsername(username, acct); err != nil {
|
|
||||||
if _, ok := err.(ErrNoEntries); ok {
|
if _, ok := err.(ErrNoEntries); ok {
|
||||||
// there are no entries for this username
|
// there are no entries for this username
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
// an actual error happened
|
||||||
return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
|
return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
|
||||||
}
|
}
|
||||||
// the user exists, we own it, we're good
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Info("could not match activityID")
|
return false, fmt.Errorf("could not match activityID: %s", id.String())
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActorForOutbox fetches the actor's IRI for the given outbox IRI.
|
// ActorForOutbox fetches the actor's IRI for the given outbox IRI.
|
||||||
//
|
//
|
||||||
// The library makes this call only after acquiring a lock first.
|
// The library makes this call only after acquiring a lock first.
|
||||||
func (f *federatingDB) ActorForOutbox(c context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) {
|
func (f *federatingDB) ActorForOutbox(c context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) {
|
||||||
return nil, nil
|
if !util.IsOutboxPath(outboxIRI) {
|
||||||
|
return nil, fmt.Errorf("%s is not an outbox URI", outboxIRI.String())
|
||||||
|
}
|
||||||
|
acct := >smodel.Account{}
|
||||||
|
if err := f.db.GetWhere("outbox_uri", outboxIRI.String(), acct); err != nil {
|
||||||
|
if _, ok := err.(ErrNoEntries); ok {
|
||||||
|
return nil, fmt.Errorf("no actor found that corresponds to outbox %s", outboxIRI.String())
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("db error searching for actor with outbox %s", outboxIRI.String())
|
||||||
|
}
|
||||||
|
return url.Parse(acct.URI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActorForInbox fetches the actor's IRI for the given outbox IRI.
|
// ActorForInbox fetches the actor's IRI for the given outbox IRI.
|
||||||
//
|
//
|
||||||
// The library makes this call only after acquiring a lock first.
|
// The library makes this call only after acquiring a lock first.
|
||||||
func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) {
|
func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) {
|
||||||
return nil, nil
|
if !util.IsInboxPath(inboxIRI) {
|
||||||
|
return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String())
|
||||||
|
}
|
||||||
|
acct := >smodel.Account{}
|
||||||
|
if err := f.db.GetWhere("inbox_uri", inboxIRI.String(), acct); err != nil {
|
||||||
|
if _, ok := err.(ErrNoEntries); ok {
|
||||||
|
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String())
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String())
|
||||||
|
}
|
||||||
|
return url.Parse(acct.URI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutboxForInbox fetches the corresponding actor's outbox IRI for the
|
// OutboxForInbox fetches the corresponding actor's outbox IRI for the
|
||||||
|
|
@ -207,7 +234,17 @@ func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (acto
|
||||||
//
|
//
|
||||||
// The library makes this call only after acquiring a lock first.
|
// The library makes this call only after acquiring a lock first.
|
||||||
func (f *federatingDB) OutboxForInbox(c context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) {
|
func (f *federatingDB) OutboxForInbox(c context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) {
|
||||||
return nil, nil
|
if !util.IsInboxPath(inboxIRI) {
|
||||||
|
return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String())
|
||||||
|
}
|
||||||
|
acct := >smodel.Account{}
|
||||||
|
if err := f.db.GetWhere("inbox_uri", inboxIRI.String(), acct); err != nil {
|
||||||
|
if _, ok := err.(ErrNoEntries); ok {
|
||||||
|
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String())
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String())
|
||||||
|
}
|
||||||
|
return url.Parse(acct.OutboxURI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exists returns true if the database has an entry for the specified
|
// Exists returns true if the database has an entry for the specified
|
||||||
|
|
|
||||||
|
|
@ -578,6 +578,7 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *gtsmodel.MediaAttachmen
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) {
|
func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) {
|
||||||
|
// TODO: check domain blocks as well
|
||||||
var blocked bool
|
var blocked bool
|
||||||
if err := ps.conn.Model(>smodel.Block{}).
|
if err := ps.conn.Model(>smodel.Block{}).
|
||||||
Where("account_id = ?", account1).Where("target_account_id = ?", account2).
|
Where("account_id = ?", account1).Where("target_account_id = ?", account2).
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,9 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
|
||||||
requestingAccount = a
|
requestingAccount = a
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, true, nil
|
contextWithRequestingAccount := context.WithValue(ctx, util.APRequestingAccount, requestingAccount)
|
||||||
|
|
||||||
|
return contextWithRequestingAccount, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocked should determine whether to permit a set of actors given by
|
// Blocked should determine whether to permit a set of actors given by
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/go-fed/activity/pub"
|
"github.com/go-fed/activity/pub"
|
||||||
|
"github.com/go-fed/activity/streams/vocab"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
|
@ -32,9 +33,17 @@ import (
|
||||||
|
|
||||||
// Federator wraps various interfaces and functions to manage activitypub federation from gotosocial
|
// Federator wraps various interfaces and functions to manage activitypub federation from gotosocial
|
||||||
type Federator interface {
|
type Federator interface {
|
||||||
|
// FederatingActor returns the underlying pub.FederatingActor, which can be used to send activities, and serve actors at inboxes/outboxes.
|
||||||
FederatingActor() pub.FederatingActor
|
FederatingActor() pub.FederatingActor
|
||||||
TransportController() transport.Controller
|
// AuthenticateFederatedRequest can be used to check the authenticity of incoming http-signed requests for federating resources.
|
||||||
|
// The given username will be used to create a transport for making outgoing requests. See the implementation for more detailed comments.
|
||||||
AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error)
|
AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error)
|
||||||
|
// DereferenceRemoteAccount can be used to get the ActivityStreamsPerson representation of a remote account, based on the account ID (which is a URI).
|
||||||
|
// The given username will be used to create a transport for making outgoing requests. See the implementation for more detailed comments.
|
||||||
|
DereferenceRemoteAccount(username string, remoteAccountID *url.URL) (vocab.ActivityStreamsPerson, error)
|
||||||
|
// GetTransportForUser returns a new transport initialized with the key credentials belonging to the given username.
|
||||||
|
// This can be used for making signed http requests.
|
||||||
|
GetTransportForUser(username string) (pub.Transport, error)
|
||||||
pub.CommonBehavior
|
pub.CommonBehavior
|
||||||
pub.FederatingProtocol
|
pub.FederatingProtocol
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +78,3 @@ func NewFederator(db db.DB, transportController transport.Controller, config *co
|
||||||
func (f *federator) FederatingActor() pub.FederatingActor {
|
func (f *federator) FederatingActor() pub.FederatingActor {
|
||||||
return f.actor
|
return f.actor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *federator) TransportController() transport.Controller {
|
|
||||||
return f.transportController
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ func (suite *ProtocolTestSuite) TestPostInboxRequestBodyHook() {
|
||||||
request.Header.Set("Signature", activity.SignatureHeader)
|
request.Header.Set("Signature", activity.SignatureHeader)
|
||||||
|
|
||||||
// trigger the function being tested, and return the new context it creates
|
// trigger the function being tested, and return the new context it creates
|
||||||
newContext, err := federator.FederatingProtocol().PostInboxRequestBodyHook(ctx, request, activity.Activity)
|
newContext, err := federator.PostInboxRequestBodyHook(ctx, request, activity.Activity)
|
||||||
assert.NoError(suite.T(), err)
|
assert.NoError(suite.T(), err)
|
||||||
assert.NotNil(suite.T(), newContext)
|
assert.NotNil(suite.T(), newContext)
|
||||||
|
|
||||||
|
|
@ -173,7 +173,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() {
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
// trigger the function being tested, and return the new context it creates
|
// trigger the function being tested, and return the new context it creates
|
||||||
newContext, authed, err := federator.FederatingProtocol().AuthenticatePostInbox(ctxWithActivity, recorder, request)
|
newContext, authed, err := federator.AuthenticatePostInbox(ctxWithActivity, recorder, request)
|
||||||
assert.NoError(suite.T(), err)
|
assert.NoError(suite.T(), err)
|
||||||
assert.True(suite.T(), authed)
|
assert.True(suite.T(), authed)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,13 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca
|
||||||
return pkpFound, nil
|
return pkpFound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthenticateFederatedRequest authenticates any kind of federated request from a remote server. This includes things like
|
// AuthenticateFederatedRequest authenticates any kind of incoming federated request from a remote server. This includes things like
|
||||||
// GET requests for dereferencing users or statuses etc and POST requests for delivering new Activities.
|
// GET requests for dereferencing our users or statuses etc, and POST requests for delivering new Activities. The function returns
|
||||||
//
|
// the URL of the owner of the public key used in the http signature.
|
||||||
// Error means the request did not pass authentication. No error means it's authentic.
|
|
||||||
//
|
//
|
||||||
// Authenticate in this case is defined as just making sure that the http request is actually signed by whoever claims
|
// Authenticate in this case is defined as just making sure that the http request is actually signed by whoever claims
|
||||||
// to have signed it, by fetching the public key from the signature and checking it against the remote public key.
|
// to have signed it, by fetching the public key from the signature and checking it against the remote public key. This function
|
||||||
|
// *does not* check whether the request is authorized, only whether it's authentic.
|
||||||
//
|
//
|
||||||
// The provided username will be used to generate a transport for making remote requests/derefencing the public key ID of the request signature.
|
// The provided username will be used to generate a transport for making remote requests/derefencing the public key ID of the request signature.
|
||||||
// Ideally you should pass in the username of the user *being requested*, so that the remote server can decide how to handle the request based on who's making it.
|
// Ideally you should pass in the username of the user *being requested*, so that the remote server can decide how to handle the request based on who's making it.
|
||||||
|
|
@ -108,8 +108,8 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca
|
||||||
//
|
//
|
||||||
// Note that it is also valid to pass in an empty string here, in which case the keys of the instance account will be used.
|
// Note that it is also valid to pass in an empty string here, in which case the keys of the instance account will be used.
|
||||||
//
|
//
|
||||||
// Note that this function *does not* dereference the remote account that the signature key is associated with, but it will
|
// Also note that this function *does not* dereference the remote account that the signature key is associated with.
|
||||||
// return the owner of the public key, so that other functions can dereference it with that, as required.
|
// Other functions should use the returned URL to dereference the remote account, if required.
|
||||||
func (f *federator) AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error) {
|
func (f *federator) AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error) {
|
||||||
verifier, err := httpsig.NewVerifier(r)
|
verifier, err := httpsig.NewVerifier(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -225,9 +225,56 @@ func (f *federator) GetTransportForUser(username string) (pub.Transport, error)
|
||||||
return nil, fmt.Errorf("error getting account %s from db: %s", username, err)
|
return nil, fmt.Errorf("error getting account %s from db: %s", username, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
transport, err := f.TransportController().NewTransport(ourAccount.PublicKeyURI, ourAccount.PrivateKey)
|
transport, err := f.transportController.NewTransport(ourAccount.PublicKeyURI, ourAccount.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating transport for user %s: %s", username, err)
|
return nil, fmt.Errorf("error creating transport for user %s: %s", username, err)
|
||||||
}
|
}
|
||||||
return transport, nil
|
return transport, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
activityStreamsContext = "https://www.w3.org/ns/activitystreams"
|
||||||
|
w3idContext = "https://w3id.org/security/v1"
|
||||||
|
tootContext = "http://joinmastodon.org/ns#"
|
||||||
|
schemaContext = "http://schema.org#"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActivityStreamsContext returns the url representation of https://www.w3.org/ns/activitystreams
|
||||||
|
func ActivityStreamsContext() *url.URL {
|
||||||
|
u, err := url.Parse(activityStreamsContext)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// W3IDContext returns the url representation of https://w3id.org/security/v1
|
||||||
|
func W3IDContext() *url.URL {
|
||||||
|
u, err := url.Parse(w3idContext)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// TootContext returns the url representation of http://joinmastodon.org/ns#
|
||||||
|
func TootContext() *url.URL {
|
||||||
|
u, err := url.Parse(tootContext)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchemaContext returns the url representation of http://schema.org#
|
||||||
|
func SchemaContext() *url.URL {
|
||||||
|
u, err := url.Parse(schemaContext)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func StandardContexts() vocab.ActivityStreamsContextProperty {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,70 @@
|
||||||
package message
|
package message
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-fed/activity/streams"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *processor) GetAPUser(requestHeaders http.Header, username string) (interface{}, error) {
|
func (p *processor) GetAPUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) {
|
||||||
|
// get the account the request is referring to
|
||||||
|
requestedAccount := >smodel.Account{}
|
||||||
|
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err))
|
||||||
|
}
|
||||||
|
|
||||||
// // get the account the request is referring to
|
// authenticate the request
|
||||||
// requestedAccount := >smodel.Account{}
|
requestingAccountURI, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request)
|
||||||
// if err := m.db.GetLocalAccountByUsername(username, requestedAccount); err != nil {
|
if err != nil {
|
||||||
// return nil, NewErrorNotAuthorized(fmt.Errorf("database error getting account with username %s: %s", username, err))
|
return nil, NewErrorNotAuthorized(err)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // and create a transport for it
|
requestingAccount := >smodel.Account{}
|
||||||
// transport, err := p.federator.TransportController().NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey)
|
err = p.db.GetWhere("uri", requestingAccountURI.String(), requestingAccount)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// l.Errorf("error creating transport for username %s: %s", requestedUsername, err)
|
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||||
// // we'll just return not authorized here to avoid giving anything away
|
// we don't have an entry for this account yet
|
||||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
// what we do now should depend on our chosen federation method
|
||||||
// return
|
// for now though, we'll just dereference it
|
||||||
// }
|
// TODO: slow-fed
|
||||||
|
requestingPerson, err := p.federator.DereferenceRemoteAccount(requestedUsername, requestingAccountURI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
requestedAccount, err = p.tc.ASPersonToAccount(requestingPerson)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
if err := p.db.Put(requestingAccount); err != nil {
|
||||||
|
return nil, NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// something has actually gone wrong
|
||||||
|
return nil, NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // authenticate the request
|
blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID)
|
||||||
// authentication, err := federation.AuthenticateFederatedRequest(transport, c.Request)
|
if err != nil {
|
||||||
// if err != nil {
|
return nil, NewErrorInternalError(err)
|
||||||
// l.Errorf("error authenticating GET user request: %s", err)
|
}
|
||||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if !authentication.Authenticated {
|
if blocked {
|
||||||
// l.Debug("request not authorized")
|
return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
|
||||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
}
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// requestingAccount := >smodel.Account{}
|
requestedPerson, err := p.tc.AccountToAS(requestedAccount)
|
||||||
// if authentication.RequestingPublicKeyID != nil {
|
if err != nil {
|
||||||
// if err := m.db.GetWhere("public_key_uri", authentication.RequestingPublicKeyID.String(), requestingAccount); err != nil {
|
return nil, NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
// }
|
data, err := streams.Serialize(requestedPerson)
|
||||||
// }
|
if err != nil {
|
||||||
|
return nil, NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
// authorization, err := federation.AuthorizeFederatedRequest
|
return data, nil
|
||||||
|
|
||||||
// person, err := m.tc.AccountToAS(requestedAccount)
|
|
||||||
// if err != nil {
|
|
||||||
// l.Errorf("error converting account to ap person: %s", err)
|
|
||||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// data, err := person.Serialize()
|
|
||||||
// if err != nil {
|
|
||||||
// l.Errorf("error serializing user: %s", err)
|
|
||||||
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// c.JSON(http.StatusOK, data)
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@
|
||||||
package message
|
package message
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
|
@ -48,7 +50,7 @@ type Processor interface {
|
||||||
FromFederator() chan FromFederator
|
FromFederator() chan FromFederator
|
||||||
|
|
||||||
/*
|
/*
|
||||||
API-FACING PROCESSING FUNCTIONS
|
CLIENT API-FACING PROCESSING FUNCTIONS
|
||||||
These functions are intended to be called when the API client needs an immediate (ie., synchronous) reply
|
These functions are intended to be called when the API client needs an immediate (ie., synchronous) reply
|
||||||
to an HTTP request. As such, they will only do the bare-minimum of work necessary to give a properly
|
to an HTTP request. As such, they will only do the bare-minimum of work necessary to give a properly
|
||||||
formed reply. For more intensive (and time-consuming) calls, where you don't require an immediate
|
formed reply. For more intensive (and time-consuming) calls, where you don't require an immediate
|
||||||
|
|
@ -82,6 +84,16 @@ type Processor interface {
|
||||||
// AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form.
|
// AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form.
|
||||||
AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error)
|
AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error)
|
||||||
|
|
||||||
|
/*
|
||||||
|
FEDERATION API-FACING PROCESSING FUNCTIONS
|
||||||
|
These functions are intended to be called when the federating client needs an immediate (ie., synchronous) reply
|
||||||
|
to an HTTP request. As such, they will only do the bare-minimum of work necessary to give a properly
|
||||||
|
formed reply. For more intensive (and time-consuming) calls, where you don't require an immediate
|
||||||
|
response, pass work to the processor using a channel instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
GetAPUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
|
||||||
|
|
||||||
// Start starts the Processor, reading from its channels and passing messages back and forth.
|
// Start starts the Processor, reading from its channels and passing messages back and forth.
|
||||||
Start() error
|
Start() error
|
||||||
// Stop stops the processor cleanly, finishing handling any remaining messages before closing down.
|
// Stop stops the processor cleanly, finishing handling any remaining messages before closing down.
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,7 @@ func (p *processor) StatusCreate(auth *oauth.Auth, form *apimodel.AdvancedStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the frontend representation of the new status to the submitter
|
// return the frontend representation of the new status to the submitter
|
||||||
mastoStatus, err := p.tc.StatusToMasto(newStatus, auth.Account, auth.Account, nil, newStatus.GTSReplyToAccount, nil)
|
return p.tc.StatusToMasto(newStatus, auth.Account, auth.Account, nil, newStatus.GTSReplyToAccount, nil)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return mastoStatus, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) StatusDelete(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
|
func (p *processor) StatusDelete(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Converts a gts model account into an Activity Streams person type, following
|
// Converts a gts model account into an Activity Streams person type, following
|
||||||
// the spec laid out for mastodon here: https://docs.joinmastodon.org/spec/activitypub/
|
// the spec laid out for mastodon here: https://docs.joinmastodon.org/spec/activitypub/
|
||||||
func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) {
|
func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-fed/activity/streams"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
@ -69,7 +70,7 @@ func (suite *InternalToASTestSuite) TestPostAccountToAS() {
|
||||||
asPerson, err := suite.typeconverter.AccountToAS(testAccount)
|
asPerson, err := suite.typeconverter.AccountToAS(testAccount)
|
||||||
assert.NoError(suite.T(), err)
|
assert.NoError(suite.T(), err)
|
||||||
|
|
||||||
ser, err := asPerson.Serialize()
|
ser, err := streams.Serialize(asPerson)
|
||||||
assert.NoError(suite.T(), err)
|
assert.NoError(suite.T(), err)
|
||||||
|
|
||||||
bytes, err := json.Marshal(ser)
|
bytes, err := json.Marshal(ser)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue