2021-05-17 17:06:58 +00:00
/ *
GoToSocial
Copyright ( C ) 2021 GoToSocial Authors admin @ gotosocial . org
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2021-05-30 11:12:00 +00:00
package processing
2021-05-08 12:25:55 +00:00
import (
2021-05-15 09:58:11 +00:00
"context"
2021-07-05 11:23:03 +00:00
"errors"
2021-05-08 12:25:55 +00:00
"fmt"
"net/http"
2021-05-21 13:48:26 +00:00
"net/url"
2021-05-08 12:25:55 +00:00
"github.com/go-fed/activity/streams"
2021-06-26 14:21:40 +00:00
"github.com/go-fed/activity/streams/vocab"
2021-05-09 18:34:27 +00:00
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
2021-05-08 12:25:55 +00:00
"github.com/superseriousbusiness/gotosocial/internal/db"
2021-06-13 16:42:28 +00:00
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
2021-05-08 12:25:55 +00:00
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
2021-06-13 16:42:28 +00:00
"github.com/superseriousbusiness/gotosocial/internal/id"
2021-05-15 09:58:11 +00:00
"github.com/superseriousbusiness/gotosocial/internal/util"
2021-05-08 12:25:55 +00:00
)
2021-06-27 09:46:07 +00:00
// dereferenceFediRequest authenticates the HTTP signature of an incoming federation request, using the given
2021-05-08 12:25:55 +00:00
// username to perform the validation. It will *also* dereference the originator of the request and return it as a gtsmodel account
// for further processing. NOTE that this function will have the side effect of putting the dereferenced account into the database,
// and passing it into the processor through a channel for further asynchronous processing.
2021-06-27 09:46:07 +00:00
func ( p * processor ) dereferenceFediRequest ( username string , requestingAccountURI * url . URL ) ( * gtsmodel . Account , error ) {
2021-05-08 12:25:55 +00:00
// OK now we can do the dereferencing part
// we might already have an entry for this account so check that first
requestingAccount := & gtsmodel . Account { }
2021-06-27 09:46:07 +00:00
err := p . db . GetWhere ( [ ] db . Where { { Key : "uri" , Value : requestingAccountURI . String ( ) } } , requestingAccount )
2021-05-08 12:25:55 +00:00
if err == nil {
// we do have it yay, return it
return requestingAccount , nil
}
if _ , ok := err . ( db . ErrNoEntries ) ; ! ok {
// something has actually gone wrong so bail
return nil , fmt . Errorf ( "database error getting account with uri %s: %s" , requestingAccountURI . String ( ) , err )
}
// we just don't have an entry for this account yet
// what we do now should depend on our chosen federation method
// for now though, we'll just dereference it
// TODO: slow-fed
requestingPerson , err := p . federator . DereferenceRemoteAccount ( username , requestingAccountURI )
if err != nil {
return nil , fmt . Errorf ( "couldn't dereference %s: %s" , requestingAccountURI . String ( ) , err )
}
// convert it to our internal account representation
2021-05-23 16:07:04 +00:00
requestingAccount , err = p . tc . ASRepresentationToAccount ( requestingPerson , false )
2021-05-08 12:25:55 +00:00
if err != nil {
return nil , fmt . Errorf ( "couldn't convert dereferenced uri %s to gtsmodel account: %s" , requestingAccountURI . String ( ) , err )
}
2021-06-13 16:42:28 +00:00
requestingAccountID , err := id . NewRandomULID ( )
if err != nil {
return nil , err
}
requestingAccount . ID = requestingAccountID
2021-05-08 12:25:55 +00:00
if err := p . db . Put ( requestingAccount ) ; err != nil {
return nil , fmt . Errorf ( "database error inserting account with uri %s: %s" , requestingAccountURI . String ( ) , err )
}
// put it in our channel to queue it for async processing
2021-06-13 16:42:28 +00:00
p . fromFederator <- gtsmodel . FromFederator {
2021-05-08 12:25:55 +00:00
APObjectType : gtsmodel . ActivityStreamsProfile ,
APActivityType : gtsmodel . ActivityStreamsCreate ,
2021-05-17 17:06:58 +00:00
GTSModel : requestingAccount ,
2021-05-08 12:25:55 +00:00
}
return requestingAccount , nil
}
2021-07-05 11:23:03 +00:00
func ( p * processor ) GetFediUser ( ctx context . Context , requestedUsername string , requestURL * url . URL ) ( interface { } , gtserror . WithCode ) {
2021-05-08 12:25:55 +00:00
// get the account the request is referring to
requestedAccount := & gtsmodel . Account { }
if err := p . db . GetLocalAccountByUsername ( requestedUsername , requestedAccount ) ; err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-08 12:25:55 +00:00
}
2021-06-26 14:21:40 +00:00
var requestedPerson vocab . ActivityStreamsPerson
var err error
2021-07-05 11:23:03 +00:00
if util . IsPublicKeyPath ( requestURL ) {
2021-06-26 14:21:40 +00:00
// if it's a public key path, we don't need to authenticate but we'll only serve the bare minimum user profile needed for the public key
requestedPerson , err = p . tc . AccountToASMinimal ( requestedAccount )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
2021-07-05 11:23:03 +00:00
} else if util . IsUserPath ( requestURL ) {
2021-06-26 14:21:40 +00:00
// if it's a user path, we want to fully authenticate the request before we serve any data, and then we can serve a more complete profile
2021-07-05 11:23:03 +00:00
requestingAccountURI , authenticated , err := p . federator . AuthenticateFederatedRequest ( ctx , requestedUsername )
if err != nil || ! authenticated {
return nil , gtserror . NewErrorNotAuthorized ( errors . New ( "not authorized" ) , "not authorized" )
2021-06-26 14:21:40 +00:00
}
2021-06-27 09:46:07 +00:00
// if we're already handshaking/dereferencing a remote account, we can skip the dereferencing part
if ! p . federator . Handshaking ( requestedUsername , requestingAccountURI ) {
requestingAccount , err := p . dereferenceFediRequest ( requestedUsername , requestingAccountURI )
if err != nil {
return nil , gtserror . NewErrorNotAuthorized ( err )
}
blocked , err := p . db . Blocked ( requestedAccount . ID , requestingAccount . ID )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
if blocked {
return nil , gtserror . NewErrorNotAuthorized ( fmt . Errorf ( "block exists between accounts %s and %s" , requestedAccount . ID , requestingAccount . ID ) )
}
2021-06-26 14:21:40 +00:00
}
requestedPerson , err = p . tc . AccountToAS ( requestedAccount )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
} else {
return nil , gtserror . NewErrorBadRequest ( fmt . Errorf ( "path was not public key path or user path" ) )
2021-05-08 12:25:55 +00:00
}
data , err := streams . Serialize ( requestedPerson )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-08 12:25:55 +00:00
}
return data , nil
}
2021-05-09 18:34:27 +00:00
2021-07-05 11:23:03 +00:00
func ( p * processor ) GetFediFollowers ( ctx context . Context , requestedUsername string , requestURL * url . URL ) ( interface { } , gtserror . WithCode ) {
2021-05-21 13:48:26 +00:00
// get the account the request is referring to
requestedAccount := & gtsmodel . Account { }
if err := p . db . GetLocalAccountByUsername ( requestedUsername , requestedAccount ) ; err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-21 13:48:26 +00:00
}
// authenticate the request
2021-07-05 11:23:03 +00:00
requestingAccountURI , authenticated , err := p . federator . AuthenticateFederatedRequest ( ctx , requestedUsername )
if err != nil || ! authenticated {
return nil , gtserror . NewErrorNotAuthorized ( errors . New ( "not authorized" ) , "not authorized" )
2021-06-27 09:46:07 +00:00
}
requestingAccount , err := p . dereferenceFediRequest ( requestedUsername , requestingAccountURI )
2021-05-21 13:48:26 +00:00
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotAuthorized ( err )
2021-05-21 13:48:26 +00:00
}
blocked , err := p . db . Blocked ( requestedAccount . ID , requestingAccount . ID )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 13:48:26 +00:00
}
if blocked {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotAuthorized ( fmt . Errorf ( "block exists between accounts %s and %s" , requestedAccount . ID , requestingAccount . ID ) )
2021-05-21 13:48:26 +00:00
}
requestedAccountURI , err := url . Parse ( requestedAccount . URI )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( fmt . Errorf ( "error parsing url %s: %s" , requestedAccount . URI , err ) )
2021-05-21 13:48:26 +00:00
}
requestedFollowers , err := p . federator . FederatingDB ( ) . Followers ( context . Background ( ) , requestedAccountURI )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( fmt . Errorf ( "error fetching followers for uri %s: %s" , requestedAccountURI . String ( ) , err ) )
2021-05-21 13:48:26 +00:00
}
data , err := streams . Serialize ( requestedFollowers )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 13:48:26 +00:00
}
return data , nil
}
2021-07-05 11:23:03 +00:00
func ( p * processor ) GetFediFollowing ( ctx context . Context , requestedUsername string , requestURL * url . URL ) ( interface { } , gtserror . WithCode ) {
2021-05-23 16:07:04 +00:00
// get the account the request is referring to
requestedAccount := & gtsmodel . Account { }
if err := p . db . GetLocalAccountByUsername ( requestedUsername , requestedAccount ) ; err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-23 16:07:04 +00:00
}
// authenticate the request
2021-07-05 11:23:03 +00:00
requestingAccountURI , authenticated , err := p . federator . AuthenticateFederatedRequest ( ctx , requestedUsername )
if err != nil || ! authenticated {
return nil , gtserror . NewErrorNotAuthorized ( errors . New ( "not authorized" ) , "not authorized" )
2021-06-27 09:46:07 +00:00
}
requestingAccount , err := p . dereferenceFediRequest ( requestedUsername , requestingAccountURI )
2021-05-23 16:07:04 +00:00
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotAuthorized ( err )
2021-05-23 16:07:04 +00:00
}
blocked , err := p . db . Blocked ( requestedAccount . ID , requestingAccount . ID )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-23 16:07:04 +00:00
}
if blocked {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotAuthorized ( fmt . Errorf ( "block exists between accounts %s and %s" , requestedAccount . ID , requestingAccount . ID ) )
2021-05-23 16:07:04 +00:00
}
requestedAccountURI , err := url . Parse ( requestedAccount . URI )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( fmt . Errorf ( "error parsing url %s: %s" , requestedAccount . URI , err ) )
2021-05-23 16:07:04 +00:00
}
requestedFollowing , err := p . federator . FederatingDB ( ) . Following ( context . Background ( ) , requestedAccountURI )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( fmt . Errorf ( "error fetching following for uri %s: %s" , requestedAccountURI . String ( ) , err ) )
2021-05-23 16:07:04 +00:00
}
data , err := streams . Serialize ( requestedFollowing )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-23 16:07:04 +00:00
}
return data , nil
}
2021-07-05 11:23:03 +00:00
func ( p * processor ) GetFediStatus ( ctx context . Context , requestedUsername string , requestedStatusID string , requestURL * url . URL ) ( interface { } , gtserror . WithCode ) {
2021-05-21 21:04:59 +00:00
// get the account the request is referring to
requestedAccount := & gtsmodel . Account { }
if err := p . db . GetLocalAccountByUsername ( requestedUsername , requestedAccount ) ; err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-21 21:04:59 +00:00
}
// authenticate the request
2021-07-05 11:23:03 +00:00
requestingAccountURI , authenticated , err := p . federator . AuthenticateFederatedRequest ( ctx , requestedUsername )
if err != nil || ! authenticated {
return nil , gtserror . NewErrorNotAuthorized ( errors . New ( "not authorized" ) , "not authorized" )
2021-06-27 09:46:07 +00:00
}
requestingAccount , err := p . dereferenceFediRequest ( requestedUsername , requestingAccountURI )
2021-05-21 21:04:59 +00:00
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotAuthorized ( err )
2021-05-21 21:04:59 +00:00
}
2021-06-17 16:02:33 +00:00
// authorize the request:
// 1. check if a block exists between the requester and the requestee
2021-05-21 21:04:59 +00:00
blocked , err := p . db . Blocked ( requestedAccount . ID , requestingAccount . ID )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 21:04:59 +00:00
}
if blocked {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotAuthorized ( fmt . Errorf ( "block exists between accounts %s and %s" , requestedAccount . ID , requestingAccount . ID ) )
2021-05-21 21:04:59 +00:00
}
2021-06-17 16:02:33 +00:00
// get the status out of the database here
2021-05-21 21:04:59 +00:00
s := & gtsmodel . Status { }
if err := p . db . GetWhere ( [ ] db . Where {
{ Key : "id" , Value : requestedStatusID } ,
{ Key : "account_id" , Value : requestedAccount . ID } ,
} , s ) ; err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting status with id %s and account id %s: %s" , requestedStatusID , requestedAccount . ID , err ) )
2021-05-21 21:04:59 +00:00
}
2021-06-17 16:02:33 +00:00
visible , err := p . filter . StatusVisible ( s , requestingAccount )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
if ! visible {
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "status with id %s not visible to user with id %s" , s . ID , requestingAccount . ID ) )
}
// requester is authorized to view the status, so convert it to AP representation and serialize it
2021-05-21 21:04:59 +00:00
asStatus , err := p . tc . StatusToAS ( s )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 21:04:59 +00:00
}
data , err := streams . Serialize ( asStatus )
if err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorInternalError ( err )
2021-05-21 21:04:59 +00:00
}
return data , nil
2021-05-21 13:48:26 +00:00
}
2021-07-05 11:23:03 +00:00
func ( p * processor ) GetWebfingerAccount ( ctx context . Context , requestedUsername string , requestURL * url . URL ) ( * apimodel . WellKnownResponse , gtserror . WithCode ) {
2021-05-09 18:34:27 +00:00
// get the account the request is referring to
requestedAccount := & gtsmodel . Account { }
if err := p . db . GetLocalAccountByUsername ( requestedUsername , requestedAccount ) ; err != nil {
2021-06-13 16:42:28 +00:00
return nil , gtserror . NewErrorNotFound ( fmt . Errorf ( "database error getting account with username %s: %s" , requestedUsername , err ) )
2021-05-09 18:34:27 +00:00
}
// return the webfinger representation
2021-06-24 12:26:08 +00:00
return & apimodel . WellKnownResponse {
2021-05-09 18:34:27 +00:00
Subject : fmt . Sprintf ( "acct:%s@%s" , requestedAccount . Username , p . config . Host ) ,
Aliases : [ ] string {
requestedAccount . URI ,
requestedAccount . URL ,
} ,
2021-06-24 12:26:08 +00:00
Links : [ ] apimodel . Link {
2021-05-09 18:34:27 +00:00
{
Rel : "http://webfinger.net/rel/profile-page" ,
Type : "text/html" ,
Href : requestedAccount . URL ,
} ,
{
Rel : "self" ,
Type : "application/activity+json" ,
Href : requestedAccount . URI ,
} ,
} ,
} , nil
}
2021-05-15 09:58:11 +00:00
2021-06-24 12:26:08 +00:00
func ( p * processor ) GetNodeInfoRel ( request * http . Request ) ( * apimodel . WellKnownResponse , gtserror . WithCode ) {
return & apimodel . WellKnownResponse {
Links : [ ] apimodel . Link {
{
Rel : "http://nodeinfo.diaspora.software/ns/schema/2.0" ,
Href : fmt . Sprintf ( "%s://%s/nodeinfo/2.0" , p . config . Protocol , p . config . Host ) ,
} ,
} ,
} , nil
}
func ( p * processor ) GetNodeInfo ( request * http . Request ) ( * apimodel . Nodeinfo , gtserror . WithCode ) {
return & apimodel . Nodeinfo {
Version : "2.0" ,
Software : apimodel . NodeInfoSoftware {
Name : "gotosocial" ,
Version : p . config . SoftwareVersion ,
} ,
Protocols : [ ] string { "activitypub" } ,
Services : apimodel . NodeInfoServices {
Inbound : [ ] string { } ,
Outbound : [ ] string { } ,
} ,
OpenRegistrations : p . config . AccountsConfig . OpenRegistration ,
Usage : apimodel . NodeInfoUsage {
Users : apimodel . NodeInfoUsers { } ,
} ,
Metadata : make ( map [ string ] interface { } ) ,
} , nil
}
2021-05-15 09:58:11 +00:00
func ( p * processor ) InboxPost ( ctx context . Context , w http . ResponseWriter , r * http . Request ) ( bool , error ) {
contextWithChannel := context . WithValue ( ctx , util . APFromFederatorChanKey , p . fromFederator )
2021-07-05 11:23:03 +00:00
return p . federator . FederatingActor ( ) . PostInbox ( contextWithChannel , w , r )
2021-05-15 09:58:11 +00:00
}