| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | /* | 
					
						
							|  |  |  |    GoToSocial | 
					
						
							| 
									
										
										
										
											2021-12-20 18:42:19 +01:00
										 |  |  |    Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |    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/>. | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package transport | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	"crypto/rsa" | 
					
						
							|  |  |  | 	"crypto/x509" | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	"codeberg.org/gruf/go-byteutil" | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 	"codeberg.org/gruf/go-cache/v3" | 
					
						
							| 
									
										
										
										
											2021-11-13 17:29:43 +01:00
										 |  |  | 	"github.com/superseriousbusiness/activity/pub" | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	"github.com/superseriousbusiness/activity/streams" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb" | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Controller generates transports for use in making federation requests to other servers. | 
					
						
							|  |  |  | type Controller interface { | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	// NewTransport returns an http signature transport with the given public key ID (URL location of pubkey), and the given private key. | 
					
						
							|  |  |  | 	NewTransport(pubKeyID string, privkey *rsa.PrivateKey) (Transport, error) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NewTransportForUsername searches for account with username, and returns result of .NewTransport(). | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	NewTransportForUsername(ctx context.Context, username string) (Transport, error) | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type controller struct { | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	db        db.DB | 
					
						
							|  |  |  | 	fedDB     federatingdb.DB | 
					
						
							|  |  |  | 	clock     pub.Clock | 
					
						
							|  |  |  | 	client    pub.HttpClient | 
					
						
							| 
									
										
										
										
											2022-10-08 12:50:16 +01:00
										 |  |  | 	trspCache cache.Cache[string, *transport] | 
					
						
							|  |  |  | 	badHosts  cache.Cache[string, struct{}] | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	userAgent string | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | // NewController returns an implementation of the Controller interface for creating new transports | 
					
						
							|  |  |  | func NewController(db db.DB, federatingDB federatingdb.DB, clock pub.Clock, client pub.HttpClient) Controller { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	applicationName := config.GetApplicationName() | 
					
						
							| 
									
										
										
										
											2022-11-27 01:09:09 +01:00
										 |  |  | 	host := config.GetHost() | 
					
						
							| 
									
										
										
										
											2022-11-26 21:15:19 +01:00
										 |  |  | 	proto := config.GetProtocol() | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	version := config.GetSoftwareVersion() | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	c := &controller{ | 
					
						
							|  |  |  | 		db:        db, | 
					
						
							|  |  |  | 		fedDB:     federatingDB, | 
					
						
							|  |  |  | 		clock:     clock, | 
					
						
							|  |  |  | 		client:    client, | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 		trspCache: cache.New[string, *transport](0, 100, 0), | 
					
						
							|  |  |  | 		badHosts:  cache.New[string, struct{}](0, 1000, 0), | 
					
						
							| 
									
										
										
										
											2022-11-26 21:15:19 +01:00
										 |  |  | 		userAgent: fmt.Sprintf("%s (+%s://%s) gotosocial/%s", applicationName, proto, host, version), | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-08 12:50:16 +01:00
										 |  |  | 	// Transport cache has TTL=1hr freq=1min | 
					
						
							|  |  |  | 	c.trspCache.SetTTL(time.Hour, false) | 
					
						
							|  |  |  | 	if !c.trspCache.Start(time.Minute) { | 
					
						
							|  |  |  | 		log.Panic("failed to start transport controller cache") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Bad hosts cache has TTL=15min freq=1min | 
					
						
							|  |  |  | 	c.badHosts.SetTTL(15*time.Minute, false) | 
					
						
							|  |  |  | 	if !c.badHosts.Start(time.Minute) { | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 		log.Panic("failed to start transport controller cache") | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	return c | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | func (c *controller) NewTransport(pubKeyID string, privkey *rsa.PrivateKey) (Transport, error) { | 
					
						
							|  |  |  | 	// Generate public key string for cache key | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// NOTE: it is safe to use the public key as the cache | 
					
						
							|  |  |  | 	// key here as we are generating it ourselves from the | 
					
						
							|  |  |  | 	// private key. If we were simply using a public key | 
					
						
							|  |  |  | 	// provided as argument that would absolutely NOT be safe. | 
					
						
							|  |  |  | 	pubStr := privkeyToPublicStr(privkey) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// First check for cached transport | 
					
						
							| 
									
										
										
										
											2022-10-08 12:50:16 +01:00
										 |  |  | 	transp, ok := c.trspCache.Get(pubStr) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	if ok { | 
					
						
							|  |  |  | 		return transp, nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	// Create the transport | 
					
						
							|  |  |  | 	transp = &transport{ | 
					
						
							|  |  |  | 		controller: c, | 
					
						
							|  |  |  | 		pubKeyID:   pubKeyID, | 
					
						
							|  |  |  | 		privkey:    privkey, | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	// Cache this transport under pubkey | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 	if !c.trspCache.Add(pubStr, transp) { | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 		var cached *transport | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-08 12:50:16 +01:00
										 |  |  | 		cached, ok = c.trspCache.Get(pubStr) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 		if !ok { | 
					
						
							|  |  |  | 			// Some ridiculous race cond. | 
					
						
							| 
									
										
										
										
											2022-10-08 12:50:16 +01:00
										 |  |  | 			c.trspCache.Set(pubStr, transp) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			// Use already cached | 
					
						
							|  |  |  | 			transp = cached | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	return transp, nil | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | func (c *controller) NewTransportForUsername(ctx context.Context, username string) (Transport, error) { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	// We need an account to use to create a transport for dereferecing something. | 
					
						
							|  |  |  | 	// If a username has been given, we can fetch the account with that username and use it. | 
					
						
							|  |  |  | 	// Otherwise, we can take the instance account and use those credentials to make the request. | 
					
						
							|  |  |  | 	var u string | 
					
						
							|  |  |  | 	if username == "" { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 		u = config.GetHost() | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		u = username | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-02 10:56:33 +01:00
										 |  |  | 	ourAccount, err := c.db.GetAccountByUsernameDomain(ctx, u, "") | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		return nil, fmt.Errorf("error getting account %s from db: %s", username, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	transport, err := c.NewTransport(ourAccount.PublicKeyURI, ourAccount.PrivateKey) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("error creating transport for user %s: %s", username, err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-09-02 10:56:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	return transport, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // dereferenceLocalFollowers is a shortcut to dereference followers of an | 
					
						
							|  |  |  | // account on this instance, without making any external api/http calls. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // It is passed to new transports, and should only be invoked when the iri.Host == this host. | 
					
						
							|  |  |  | func (c *controller) dereferenceLocalFollowers(ctx context.Context, iri *url.URL) ([]byte, error) { | 
					
						
							|  |  |  | 	followers, err := c.fedDB.Followers(ctx, iri) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	i, err := streams.Serialize(followers) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return json.Marshal(i) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // dereferenceLocalUser is a shortcut to dereference followers an account on | 
					
						
							|  |  |  | // this instance, without making any external api/http calls. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // It is passed to new transports, and should only be invoked when the iri.Host == this host. | 
					
						
							|  |  |  | func (c *controller) dereferenceLocalUser(ctx context.Context, iri *url.URL) ([]byte, error) { | 
					
						
							|  |  |  | 	user, err := c.fedDB.Get(ctx, iri) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	i, err := streams.Serialize(user) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return json.Marshal(i) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // privkeyToPublicStr will create a string representation of RSA public key from private. | 
					
						
							|  |  |  | func privkeyToPublicStr(privkey *rsa.PrivateKey) string { | 
					
						
							|  |  |  | 	b := x509.MarshalPKCS1PublicKey(&privkey.PublicKey) | 
					
						
							|  |  |  | 	return byteutil.B2S(b) | 
					
						
							|  |  |  | } |