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 }