gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

controller.go (6051B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package transport
     19 
     20 import (
     21 	"context"
     22 	"crypto/rsa"
     23 	"crypto/x509"
     24 	"encoding/json"
     25 	"fmt"
     26 	"net/url"
     27 	"runtime"
     28 
     29 	"codeberg.org/gruf/go-byteutil"
     30 	"codeberg.org/gruf/go-cache/v3"
     31 	"github.com/superseriousbusiness/activity/pub"
     32 	"github.com/superseriousbusiness/gotosocial/internal/ap"
     33 	"github.com/superseriousbusiness/gotosocial/internal/config"
     34 	"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
     35 	"github.com/superseriousbusiness/gotosocial/internal/httpclient"
     36 	"github.com/superseriousbusiness/gotosocial/internal/state"
     37 )
     38 
     39 // Controller generates transports for use in making federation requests to other servers.
     40 type Controller interface {
     41 	// NewTransport returns an http signature transport with the given public key ID (URL location of pubkey), and the given private key.
     42 	NewTransport(pubKeyID string, privkey *rsa.PrivateKey) (Transport, error)
     43 
     44 	// NewTransportForUsername searches for account with username, and returns result of .NewTransport().
     45 	NewTransportForUsername(ctx context.Context, username string) (Transport, error)
     46 }
     47 
     48 type controller struct {
     49 	state     *state.State
     50 	fedDB     federatingdb.DB
     51 	clock     pub.Clock
     52 	client    httpclient.SigningClient
     53 	trspCache cache.Cache[string, *transport]
     54 	userAgent string
     55 	senders   int // no. concurrent batch delivery routines.
     56 }
     57 
     58 // NewController returns an implementation of the Controller interface for creating new transports
     59 func NewController(state *state.State, federatingDB federatingdb.DB, clock pub.Clock, client httpclient.SigningClient) Controller {
     60 	var (
     61 		applicationName  = config.GetApplicationName()
     62 		host             = config.GetHost()
     63 		proto            = config.GetProtocol()
     64 		version          = config.GetSoftwareVersion()
     65 		senderMultiplier = config.GetAdvancedSenderMultiplier()
     66 	)
     67 
     68 	senders := senderMultiplier * runtime.GOMAXPROCS(0)
     69 	if senders < 1 {
     70 		// Clamp senders to 1.
     71 		senders = 1
     72 	}
     73 
     74 	c := &controller{
     75 		state:     state,
     76 		fedDB:     federatingDB,
     77 		clock:     clock,
     78 		client:    client,
     79 		trspCache: cache.New[string, *transport](0, 100, 0),
     80 		userAgent: fmt.Sprintf("%s (+%s://%s) gotosocial/%s", applicationName, proto, host, version),
     81 		senders:   senders,
     82 	}
     83 
     84 	return c
     85 }
     86 
     87 func (c *controller) NewTransport(pubKeyID string, privkey *rsa.PrivateKey) (Transport, error) {
     88 	// Generate public key string for cache key
     89 	//
     90 	// NOTE: it is safe to use the public key as the cache
     91 	// key here as we are generating it ourselves from the
     92 	// private key. If we were simply using a public key
     93 	// provided as argument that would absolutely NOT be safe.
     94 	pubStr := privkeyToPublicStr(privkey)
     95 
     96 	// First check for cached transport
     97 	transp, ok := c.trspCache.Get(pubStr)
     98 	if ok {
     99 		return transp, nil
    100 	}
    101 
    102 	// Create the transport
    103 	transp = &transport{
    104 		controller: c,
    105 		pubKeyID:   pubKeyID,
    106 		privkey:    privkey,
    107 	}
    108 
    109 	// Cache this transport under pubkey
    110 	if !c.trspCache.Add(pubStr, transp) {
    111 		var cached *transport
    112 
    113 		cached, ok = c.trspCache.Get(pubStr)
    114 		if !ok {
    115 			// Some ridiculous race cond.
    116 			c.trspCache.Set(pubStr, transp)
    117 		} else {
    118 			// Use already cached
    119 			transp = cached
    120 		}
    121 	}
    122 
    123 	return transp, nil
    124 }
    125 
    126 func (c *controller) NewTransportForUsername(ctx context.Context, username string) (Transport, error) {
    127 	// We need an account to use to create a transport for dereferecing something.
    128 	// If a username has been given, we can fetch the account with that username and use it.
    129 	// Otherwise, we can take the instance account and use those credentials to make the request.
    130 	var u string
    131 	if username == "" {
    132 		u = config.GetHost()
    133 	} else {
    134 		u = username
    135 	}
    136 
    137 	ourAccount, err := c.state.DB.GetAccountByUsernameDomain(ctx, u, "")
    138 	if err != nil {
    139 		return nil, fmt.Errorf("error getting account %s from db: %s", username, err)
    140 	}
    141 
    142 	transport, err := c.NewTransport(ourAccount.PublicKeyURI, ourAccount.PrivateKey)
    143 	if err != nil {
    144 		return nil, fmt.Errorf("error creating transport for user %s: %s", username, err)
    145 	}
    146 
    147 	return transport, nil
    148 }
    149 
    150 // dereferenceLocalFollowers is a shortcut to dereference followers of an
    151 // account on this instance, without making any external api/http calls.
    152 //
    153 // It is passed to new transports, and should only be invoked when the iri.Host == this host.
    154 func (c *controller) dereferenceLocalFollowers(ctx context.Context, iri *url.URL) ([]byte, error) {
    155 	followers, err := c.fedDB.Followers(ctx, iri)
    156 	if err != nil {
    157 		return nil, err
    158 	}
    159 
    160 	i, err := ap.Serialize(followers)
    161 	if err != nil {
    162 		return nil, err
    163 	}
    164 
    165 	return json.Marshal(i)
    166 }
    167 
    168 // dereferenceLocalUser is a shortcut to dereference followers an account on
    169 // this instance, without making any external api/http calls.
    170 //
    171 // It is passed to new transports, and should only be invoked when the iri.Host == this host.
    172 func (c *controller) dereferenceLocalUser(ctx context.Context, iri *url.URL) ([]byte, error) {
    173 	user, err := c.fedDB.Get(ctx, iri)
    174 	if err != nil {
    175 		return nil, err
    176 	}
    177 
    178 	i, err := ap.Serialize(user)
    179 	if err != nil {
    180 		return nil, err
    181 	}
    182 
    183 	return json.Marshal(i)
    184 }
    185 
    186 // privkeyToPublicStr will create a string representation of RSA public key from private.
    187 func privkeyToPublicStr(privkey *rsa.PrivateKey) string {
    188 	b := x509.MarshalPKCS1PublicKey(&privkey.PublicKey)
    189 	return byteutil.B2S(b)
    190 }