mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 16:12:24 -05:00 
			
		
		
		
	[chore/security] refactor AuthenticateFederatedRequest() to handle account deref + suspension checks (#2371)
* refactor AuthenticateFederatedRequest() to handle account suspension + fetching of owner * small fixups * small changes * revert to 'IsEitherBlocked' instead of just 'IsBlocked" :grimace: * update code comment to indicate that AuthenticateFederatedRequest() will handle account + instance dereferencing
This commit is contained in:
		
					parent
					
						
							
								1ba3e14b36
							
						
					
				
			
			
				commit
				
					
						42d8011ff4
					
				
			
		
					 7 changed files with 205 additions and 198 deletions
				
			
		|  | @ -41,11 +41,6 @@ import ( | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	errUnsigned = errors.New("http request wasn't signed or http signature was invalid") | 	errUnsigned = errors.New("http request wasn't signed or http signature was invalid") | ||||||
| 	signingAlgorithms = []httpsig.Algorithm{ |  | ||||||
| 		httpsig.RSA_SHA256, // Prefer common RSA_SHA256. |  | ||||||
| 		httpsig.RSA_SHA512, // Fall back to less common RSA_SHA512. |  | ||||||
| 		httpsig.ED25519,    // Try ED25519 as a long shot. |  | ||||||
| 	} |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // PubKeyAuth models authorization information for a remote | // PubKeyAuth models authorization information for a remote | ||||||
|  | @ -67,21 +62,18 @@ type PubKeyAuth struct { | ||||||
| 
 | 
 | ||||||
| 	// OwnerURI is the ActivityPub id of the owner of | 	// OwnerURI is the ActivityPub id of the owner of | ||||||
| 	// the public key used to sign the request we're | 	// the public key used to sign the request we're | ||||||
| 	// now authenticating. This will always be set | 	// now authenticating. This will always be set. | ||||||
| 	// even if Owner isn't, so that callers can use |  | ||||||
| 	// this URI to go fetch the Owner from remote. |  | ||||||
| 	OwnerURI *url.URL | 	OwnerURI *url.URL | ||||||
| 
 | 
 | ||||||
| 	// Owner is the account corresponding to OwnerURI. | 	// Owner is the account corresponding to | ||||||
| 	// | 	// OwnerURI. This will always be set UNLESS | ||||||
| 	// Owner will only be defined if the account who | 	// the PubKeyAuth.Handshaking field is set.. | ||||||
| 	// owns the public key was already cached in the |  | ||||||
| 	// database when we received the request we're now |  | ||||||
| 	// authenticating (ie., we've seen it before). |  | ||||||
| 	// |  | ||||||
| 	// If it's not defined, callers should use OwnerURI |  | ||||||
| 	// to go and dereference it. |  | ||||||
| 	Owner *gtsmodel.Account | 	Owner *gtsmodel.Account | ||||||
|  | 
 | ||||||
|  | 	// Handshaking indicates that uncached owner | ||||||
|  | 	// account was NOT dereferenced due to an ongoing | ||||||
|  | 	// handshake with another instance. | ||||||
|  | 	Handshaking bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AuthenticateFederatedRequest authenticates any kind of incoming federated | // AuthenticateFederatedRequest authenticates any kind of incoming federated | ||||||
|  | @ -103,12 +95,12 @@ type PubKeyAuth struct { | ||||||
| // know that this is the user making the dereferencing request, and they can decide | // know that this is the user making the dereferencing request, and they can decide | ||||||
| // to allow or deny the request depending on their settings. | // to allow or deny the request depending on their settings. | ||||||
| // | // | ||||||
|  | // This function will handle dereferencing and storage of any new remote accounts | ||||||
|  | // and / or instances. The returned PubKeyAuth{}.Owner account will ONLY ever be | ||||||
|  | // nil in the case that there is an ongoing handshake involving this account. | ||||||
|  | // | ||||||
| // Note that it is also valid to pass in an empty string here, in which case the | // 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. | // keys of the instance account will be used. | ||||||
| // |  | ||||||
| // Also note that this function *does not* dereference the remote account that |  | ||||||
| // the signature key is associated with. Other functions should use the returned |  | ||||||
| // URL to dereference the remote account, if required. |  | ||||||
| func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedUsername string) (*PubKeyAuth, gtserror.WithCode) { | func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedUsername string) (*PubKeyAuth, gtserror.WithCode) { | ||||||
| 	// Thanks to the signature check middleware, | 	// Thanks to the signature check middleware, | ||||||
| 	// we should already have an http signature | 	// we should already have an http signature | ||||||
|  | @ -142,7 +134,7 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU | ||||||
| 
 | 
 | ||||||
| 	var ( | 	var ( | ||||||
| 		pubKeyIDStr = pubKeyID.String() | 		pubKeyIDStr = pubKeyID.String() | ||||||
| 		local       = (pubKeyID.Host == config.GetHost()) | 		isLocal     = (pubKeyID.Host == config.GetHost()) | ||||||
| 		pubKeyAuth  *PubKeyAuth | 		pubKeyAuth  *PubKeyAuth | ||||||
| 		errWithCode gtserror.WithCode | 		errWithCode gtserror.WithCode | ||||||
| 	) | 	) | ||||||
|  | @ -154,7 +146,7 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU | ||||||
| 			{"pubKeyID", pubKeyIDStr}, | 			{"pubKeyID", pubKeyIDStr}, | ||||||
| 		}...) | 		}...) | ||||||
| 
 | 
 | ||||||
| 	if local { | 	if isLocal { | ||||||
| 		l.Trace("public key is local, no dereference needed") | 		l.Trace("public key is local, no dereference needed") | ||||||
| 		pubKeyAuth, errWithCode = f.derefPubKeyDBOnly(ctx, pubKeyIDStr) | 		pubKeyAuth, errWithCode = f.derefPubKeyDBOnly(ctx, pubKeyIDStr) | ||||||
| 	} else { | 	} else { | ||||||
|  | @ -166,7 +158,7 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU | ||||||
| 		return nil, errWithCode | 		return nil, errWithCode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if local && pubKeyAuth == nil { | 	if isLocal && pubKeyAuth == nil { | ||||||
| 		// We signed this request, apparently, but | 		// We signed this request, apparently, but | ||||||
| 		// local lookup didn't find anything. This | 		// local lookup didn't find anything. This | ||||||
| 		// is an almost impossible error condition! | 		// is an almost impossible error condition! | ||||||
|  | @ -175,37 +167,61 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Try to authenticate using permitted algorithms in | 	// Attempt to verify auth with both fetched and cached keys. | ||||||
| 	// order of most -> least common, checking each defined | 	if !verifyAuth(&l, verifier, pubKeyAuth.CachedPubKey) && | ||||||
| 	// pubKey for this Actor. Return OK as soon as one passes. | 		!verifyAuth(&l, verifier, pubKeyAuth.FetchedPubKey) { | ||||||
| 	for _, pubKey := range [2]*rsa.PublicKey{ | 
 | ||||||
| 		pubKeyAuth.FetchedPubKey, | 		const format = "authentication NOT PASSED for public key %s; tried algorithms %+v; signature value was '%s'" | ||||||
| 		pubKeyAuth.CachedPubKey, | 		text := fmt.Sprintf(format, pubKeyIDStr, signingAlgorithms, signature) | ||||||
| 	} { | 		return nil, gtserror.NewErrorUnauthorized(errors.New(text), text) | ||||||
| 		if pubKey == nil { |  | ||||||
| 			continue |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		for _, algo := range signingAlgorithms { | 	if pubKeyAuth.Owner == nil { | ||||||
| 			l.Tracef("trying %s", algo) | 		// Ensure we have instance stored in | ||||||
|  | 		// database for the account at URI. | ||||||
|  | 		err := f.fetchAccountInstance(ctx, | ||||||
|  | 			requestedUsername, | ||||||
|  | 			pubKeyAuth.OwnerURI, | ||||||
|  | 		) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, gtserror.NewErrorInternalError(err) | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 			err := verifier.Verify(pubKey, algo) | 		// If we're currently handshaking with another instance, return | ||||||
| 			if err == nil { | 		// without derefing the owner, the only possible time we do this. | ||||||
| 				l.Tracef("authentication PASSED with %s", algo) | 		// This prevents deadlocks when GTS instances mutually deref. | ||||||
|  | 		if f.Handshaking(requestedUsername, pubKeyAuth.OwnerURI) { | ||||||
|  | 			log.Warnf(ctx, "network race during %s handshake", pubKeyAuth.OwnerURI) | ||||||
|  | 			pubKeyAuth.Handshaking = true | ||||||
| 			return pubKeyAuth, nil | 			return pubKeyAuth, nil | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 			l.Tracef("authentication NOT PASSED with %s: %q", algo, err) | 		// Dereference the account located at owner URI. | ||||||
| 		} | 		pubKeyAuth.Owner, _, err = f.GetAccountByURI(ctx, | ||||||
| 	} | 			requestedUsername, | ||||||
| 
 | 			pubKeyAuth.OwnerURI, | ||||||
| 	// At this point no algorithms passed. |  | ||||||
| 	err := gtserror.Newf( |  | ||||||
| 		"authentication NOT PASSED for public key %s; tried algorithms %+v; signature value was '%s'", |  | ||||||
| 		pubKeyIDStr, signature, signingAlgorithms, |  | ||||||
| 		) | 		) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if gtserror.StatusCode(err) == http.StatusGone { | ||||||
|  | 				// This can happen here instead of the pubkey 'gone' | ||||||
|  | 				// checks due to: the server sending account deletion | ||||||
|  | 				// notifications out, we start processing, the request above | ||||||
|  | 				// succeeds, and *then* the profile is removed and starts | ||||||
|  | 				// returning 410 Gone, at which point _this_ request fails. | ||||||
|  | 				return nil, gtserror.NewErrorGone(err) | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 	return nil, gtserror.NewErrorUnauthorized(err, err.Error()) | 			err := gtserror.Newf("error dereferencing account %s: %w", pubKeyAuth.OwnerURI, err) | ||||||
|  | 			return nil, gtserror.NewErrorInternalError(err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !pubKeyAuth.Owner.SuspendedAt.IsZero() { | ||||||
|  | 		const text = "requesting account suspended" | ||||||
|  | 		return nil, gtserror.NewErrorForbidden(errors.New(text)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return pubKeyAuth, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // derefPubKeyDBOnly tries to dereference the given | // derefPubKeyDBOnly tries to dereference the given | ||||||
|  | @ -218,22 +234,27 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU | ||||||
| func (f *Federator) derefPubKeyDBOnly( | func (f *Federator) derefPubKeyDBOnly( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	pubKeyIDStr string, | 	pubKeyIDStr string, | ||||||
| ) (*PubKeyAuth, gtserror.WithCode) { | ) ( | ||||||
|  | 	*PubKeyAuth, | ||||||
|  | 	gtserror.WithCode, | ||||||
|  | ) { | ||||||
|  | 	// Look for pubkey ID owner in the database. | ||||||
| 	owner, err := f.db.GetAccountByPubkeyID(ctx, pubKeyIDStr) | 	owner, err := f.db.GetAccountByPubkeyID(ctx, pubKeyIDStr) | ||||||
| 	if err != nil { | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||||
| 		if errors.Is(err, db.ErrNoEntries) { | 		err = gtserror.Newf("db error getting account with pubKeyID %s: %w", pubKeyIDStr, err) | ||||||
|  | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if owner == nil { | ||||||
| 		// We don't have this | 		// We don't have this | ||||||
| 		// account stored (yet). | 		// account stored (yet). | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		err = gtserror.Newf("db error getting account with pubKeyID %s: %w", pubKeyIDStr, err) | 	// Parse owner account URI as URL obj. | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ownerURI, err := url.Parse(owner.URI) | 	ownerURI, err := url.Parse(owner.URI) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err = gtserror.Newf("error parsing account uri with pubKeyID %s: %w", pubKeyIDStr, err) | 		err := gtserror.Newf("error parsing account uri with pubKeyID %s: %w", pubKeyIDStr, err) | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -253,7 +274,10 @@ func (f *Federator) derefPubKey( | ||||||
| 	requestedUsername string, | 	requestedUsername string, | ||||||
| 	pubKeyIDStr string, | 	pubKeyIDStr string, | ||||||
| 	pubKeyID *url.URL, | 	pubKeyID *url.URL, | ||||||
| ) (*PubKeyAuth, gtserror.WithCode) { | ) ( | ||||||
|  | 	*PubKeyAuth, | ||||||
|  | 	gtserror.WithCode, | ||||||
|  | ) { | ||||||
| 	l := log. | 	l := log. | ||||||
| 		WithContext(ctx). | 		WithContext(ctx). | ||||||
| 		WithFields(kv.Fields{ | 		WithFields(kv.Fields{ | ||||||
|  | @ -316,7 +340,7 @@ func (f *Federator) derefPubKey( | ||||||
| 	// Extract the key and the owner from the response. | 	// Extract the key and the owner from the response. | ||||||
| 	pubKey, pubKeyOwner, err := parsePubKeyBytes(ctx, pubKeyBytes, pubKeyID) | 	pubKey, pubKeyOwner, err := parsePubKeyBytes(ctx, pubKeyBytes, pubKeyID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err := fmt.Errorf("error parsing public key (%s): %w", pubKeyID, err) | 		err := gtserror.Newf("error parsing public key (%s): %w", pubKeyID, err) | ||||||
| 		return nil, gtserror.NewErrorUnauthorized(err) | 		return nil, gtserror.NewErrorUnauthorized(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -337,16 +361,12 @@ func (f *Federator) derefPubKey( | ||||||
| 	// had an owner stored for it locally. Since | 	// had an owner stored for it locally. Since | ||||||
| 	// we now successfully refreshed the pub key, | 	// we now successfully refreshed the pub key, | ||||||
| 	// we should update the account to reflect that. | 	// we should update the account to reflect that. | ||||||
| 	ownerAcct := pubKeyAuth.Owner | 	owner := pubKeyAuth.Owner | ||||||
| 	ownerAcct.PublicKey = pubKeyAuth.FetchedPubKey | 	owner.PublicKey = pubKeyAuth.FetchedPubKey | ||||||
| 	ownerAcct.PublicKeyExpiresAt = time.Time{} | 	owner.PublicKeyExpiresAt = time.Time{} | ||||||
| 
 |  | ||||||
| 	l.Info("obtained a new public key to replace expired key, caching now; " + |  | ||||||
| 		"authorization for this request will be attempted with both old and new keys") |  | ||||||
| 
 |  | ||||||
| 	if err := f.db.UpdateAccount( | 	if err := f.db.UpdateAccount( | ||||||
| 		ctx, | 		ctx, | ||||||
| 		ownerAcct, | 		owner, | ||||||
| 		"public_key", | 		"public_key", | ||||||
| 		"public_key_expires_at", | 		"public_key_expires_at", | ||||||
| 	); err != nil { | 	); err != nil { | ||||||
|  | @ -354,6 +374,8 @@ func (f *Federator) derefPubKey( | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	l.Info("obtained new public key to replace expired; attempting auth with old / new") | ||||||
|  | 
 | ||||||
| 	// Return both new and cached (now | 	// Return both new and cached (now | ||||||
| 	// expired) keys, authentication | 	// expired) keys, authentication | ||||||
| 	// will be attempted with both. | 	// will be attempted with both. | ||||||
|  | @ -406,6 +428,47 @@ func (f *Federator) callForPubKey( | ||||||
| 	return nil, gtserror.NewErrorInternalError(err) | 	return nil, gtserror.NewErrorInternalError(err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // fetchAccountInstance ensures that an instance model exists in | ||||||
|  | // the database for the given account URI, deref'ing if necessary. | ||||||
|  | func (f *Federator) fetchAccountInstance( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	requestedUsername string, | ||||||
|  | 	accountURI *url.URL, | ||||||
|  | ) error { | ||||||
|  | 	// Look for an existing entry for instance in database. | ||||||
|  | 	instance, err := f.db.GetInstance(ctx, accountURI.Host) | ||||||
|  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||||
|  | 		return gtserror.Newf("error getting instance from database: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if instance != nil { | ||||||
|  | 		// already fetched. | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// We don't have an entry for this | ||||||
|  | 	// instance yet; go dereference it. | ||||||
|  | 	instance, err = f.GetRemoteInstance( | ||||||
|  | 		gtscontext.SetFastFail(ctx), | ||||||
|  | 		requestedUsername, | ||||||
|  | 		&url.URL{ | ||||||
|  | 			Scheme: accountURI.Scheme, | ||||||
|  | 			Host:   accountURI.Host, | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return gtserror.Newf("error dereferencing instance %s: %w", accountURI.Host, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Insert new instance into the datbase. | ||||||
|  | 	err = f.db.PutInstance(ctx, instance) | ||||||
|  | 	if err != nil && !errors.Is(err, db.ErrAlreadyExists) { | ||||||
|  | 		return gtserror.Newf("error inserting instance %s into database: %w", accountURI.Host, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // parsePubKeyBytes extracts an rsa public key from the | // parsePubKeyBytes extracts an rsa public key from the | ||||||
| // given pubKeyBytes by trying to parse the pubKeyBytes | // given pubKeyBytes by trying to parse the pubKeyBytes | ||||||
| // as an ActivityPub type. It will return the public key | // as an ActivityPub type. It will return the public key | ||||||
|  | @ -439,3 +502,32 @@ func parsePubKeyBytes( | ||||||
| 
 | 
 | ||||||
| 	return pubKey, pubKeyOwnerID, nil | 	return pubKey, pubKeyOwnerID, nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | var signingAlgorithms = []httpsig.Algorithm{ | ||||||
|  | 	httpsig.RSA_SHA256, // Prefer common RSA_SHA256. | ||||||
|  | 	httpsig.RSA_SHA512, // Fall back to less common RSA_SHA512. | ||||||
|  | 	httpsig.ED25519,    // Try ED25519 as a long shot. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // verifyAuth verifies auth using generated verifier, according to pubkey and our supported signing algorithms. | ||||||
|  | func verifyAuth(l *log.Entry, verifier httpsig.Verifier, pubKey *rsa.PublicKey) bool { | ||||||
|  | 	if pubKey == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Loop through all supported algorithms. | ||||||
|  | 	for _, algo := range signingAlgorithms { | ||||||
|  | 
 | ||||||
|  | 		// Verify according to pubkey and algo. | ||||||
|  | 		err := verifier.Verify(pubKey, algo) | ||||||
|  | 		if err != nil { | ||||||
|  | 			l.Tracef("authentication NOT PASSED with %s: %v", algo, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		l.Tracef("authenticated PASSED with %s", algo) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -38,8 +38,11 @@ func (d *Dereferencer) Handshaking(username string, remoteAccountID *url.URL) bo | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Calculate remote account ID str once. | ||||||
|  | 	remoteIDStr := remoteAccountID.String() | ||||||
|  | 
 | ||||||
| 	for _, id := range remoteIDs { | 	for _, id := range remoteIDs { | ||||||
| 		if id.String() == remoteAccountID.String() { | 		if id.String() == remoteIDStr { | ||||||
| 			// We are currently handshaking | 			// We are currently handshaking | ||||||
| 			// with the remote account. | 			// with the remote account. | ||||||
| 			return true | 			return true | ||||||
|  |  | ||||||
|  | @ -232,72 +232,17 @@ func (f *Federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pubKeyOwnerURI := pubKeyAuth.OwnerURI | 	if pubKeyAuth.Handshaking { | ||||||
| 
 | 		// There is a mutal handshake occurring between us and | ||||||
| 	// Authentication has passed, check if we need to create a | 		// the owner URI. Return 202 and leave as we can't do | ||||||
| 	// new instance entry for the Host of the requesting account. | 		// much else until the handshake procedure has finished. | ||||||
| 	if _, err := f.db.GetInstance(ctx, pubKeyOwnerURI.Host); err != nil { |  | ||||||
| 		if !errors.Is(err, db.ErrNoEntries) { |  | ||||||
| 			// There's been an actual error. |  | ||||||
| 			err = gtserror.Newf("error getting instance %s: %w", pubKeyOwnerURI.Host, err) |  | ||||||
| 			return ctx, false, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// We don't have an entry for this |  | ||||||
| 		// instance yet; go dereference it. |  | ||||||
| 		instance, err := f.GetRemoteInstance( |  | ||||||
| 			gtscontext.SetFastFail(ctx), |  | ||||||
| 			username, |  | ||||||
| 			&url.URL{ |  | ||||||
| 				Scheme: pubKeyOwnerURI.Scheme, |  | ||||||
| 				Host:   pubKeyOwnerURI.Host, |  | ||||||
| 			}, |  | ||||||
| 		) |  | ||||||
| 		if err != nil { |  | ||||||
| 			err = gtserror.Newf("error dereferencing instance %s: %w", pubKeyOwnerURI.Host, err) |  | ||||||
| 			return nil, false, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if err := f.db.PutInstance(ctx, instance); err != nil && !errors.Is(err, db.ErrAlreadyExists) { |  | ||||||
| 			err = gtserror.Newf("error inserting instance entry for %s: %w", pubKeyOwnerURI.Host, err) |  | ||||||
| 			return nil, false, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// We know the public key owner URI now, so we can |  | ||||||
| 	// dereference the remote account (or just get it |  | ||||||
| 	// from the db if we already have it). |  | ||||||
| 	requestingAccount, _, err := f.GetAccountByURI( |  | ||||||
| 		gtscontext.SetFastFail(ctx), |  | ||||||
| 		username, |  | ||||||
| 		pubKeyOwnerURI, |  | ||||||
| 	) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if gtserror.StatusCode(err) == http.StatusGone { |  | ||||||
| 			// This is the same case as the http.StatusGone check above. |  | ||||||
| 			// It can happen here and not there because there's a race |  | ||||||
| 			// where the sending server starts sending account deletion |  | ||||||
| 			// notifications out, we start processing, the request above |  | ||||||
| 			// succeeds, and *then* the profile is removed and starts |  | ||||||
| 			// returning 410 Gone, at which point _this_ request fails. |  | ||||||
| 		w.WriteHeader(http.StatusAccepted) | 		w.WriteHeader(http.StatusAccepted) | ||||||
| 		return ctx, false, nil | 		return ctx, false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		err = gtserror.Newf("couldn't get requesting account %s: %w", pubKeyOwnerURI, err) |  | ||||||
| 		return nil, false, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !requestingAccount.SuspendedAt.IsZero() { |  | ||||||
| 		// Account was marked as suspended by a |  | ||||||
| 		// local admin action. Stop request early. |  | ||||||
| 		w.WriteHeader(http.StatusForbidden) |  | ||||||
| 		return ctx, false, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// We have everything we need now, set the requesting | 	// We have everything we need now, set the requesting | ||||||
| 	// and receiving accounts on the context for later use. | 	// and receiving accounts on the context for later use. | ||||||
| 	ctx = gtscontext.SetRequestingAccount(ctx, requestingAccount) | 	ctx = gtscontext.SetRequestingAccount(ctx, pubKeyAuth.Owner) | ||||||
| 	ctx = gtscontext.SetReceivingAccount(ctx, receivingAccount) | 	ctx = gtscontext.SetReceivingAccount(ctx, receivingAccount) | ||||||
| 	return ctx, true, nil | 	return ctx, true, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -71,9 +71,7 @@ func (p *Processor) domainKeysExpireSideEffects(ctx context.Context, domain stri | ||||||
| 	// the public key and update the account. | 	// the public key and update the account. | ||||||
| 	if err := p.rangeDomainAccounts(ctx, domain, func(account *gtsmodel.Account) { | 	if err := p.rangeDomainAccounts(ctx, domain, func(account *gtsmodel.Account) { | ||||||
| 		account.PublicKeyExpiresAt = expiresAt | 		account.PublicKeyExpiresAt = expiresAt | ||||||
| 
 | 		if err := p.state.DB.UpdateAccount(ctx, | ||||||
| 		if err := p.state.DB.UpdateAccount( |  | ||||||
| 			ctx, |  | ||||||
| 			account, | 			account, | ||||||
| 			"public_key_expires_at", | 			"public_key_expires_at", | ||||||
| 		); err != nil { | 		); err != nil { | ||||||
|  |  | ||||||
|  | @ -22,7 +22,6 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| ) | ) | ||||||
|  | @ -45,8 +44,6 @@ func (p *Processor) authenticate(ctx context.Context, requestedUser string) ( | ||||||
| 		return nil, nil, gtserror.NewErrorNotFound(err) | 		return nil, nil, gtserror.NewErrorNotFound(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var requester *gtsmodel.Account |  | ||||||
| 
 |  | ||||||
| 	// Ensure request signed, and use signature URI to | 	// Ensure request signed, and use signature URI to | ||||||
| 	// get requesting account, dereferencing if necessary. | 	// get requesting account, dereferencing if necessary. | ||||||
| 	pubKeyAuth, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUser) | 	pubKeyAuth, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUser) | ||||||
|  | @ -54,33 +51,23 @@ func (p *Processor) authenticate(ctx context.Context, requestedUser string) ( | ||||||
| 		return nil, nil, errWithCode | 		return nil, nil, errWithCode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if requester = pubKeyAuth.Owner; requester == nil { | 	if pubKeyAuth.Handshaking { | ||||||
| 		requester, _, err = p.federator.GetAccountByURI( | 		// This should happen very rarely, we are in the middle of handshaking. | ||||||
| 			gtscontext.SetFastFail(ctx), | 		err := gtserror.Newf("network race handshaking %s", pubKeyAuth.OwnerURI) | ||||||
| 			requestedUser, | 		return nil, nil, gtserror.NewErrorInternalError(err) | ||||||
| 			pubKeyAuth.OwnerURI, |  | ||||||
| 		) |  | ||||||
| 		if err != nil { |  | ||||||
| 			err = gtserror.Newf("error getting account %s: %w", pubKeyAuth.OwnerURI, err) |  | ||||||
| 			return nil, nil, gtserror.NewErrorUnauthorized(err) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !requester.SuspendedAt.IsZero() { | 	// Get requester from auth. | ||||||
| 		// Account was marked as suspended by a | 	requester := pubKeyAuth.Owner | ||||||
| 		// local admin action. Stop request early. |  | ||||||
| 		const text = "requesting account is suspended" |  | ||||||
| 		return nil, nil, gtserror.NewErrorForbidden(errors.New(text)) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Ensure no block exists between requester + requested. | 	// Check that block does not exist between receiver and requester. | ||||||
| 	blocked, err := p.state.DB.IsEitherBlocked(ctx, receiver.ID, requester.ID) | 	blocked, err := p.state.DB.IsEitherBlocked(ctx, receiver.ID, requester.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err = gtserror.Newf("db error getting checking block: %w", err) | 		err := gtserror.Newf("error checking block: %w", err) | ||||||
| 		return nil, nil, gtserror.NewErrorInternalError(err) | 		return nil, nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} else if blocked { | 	} else if blocked { | ||||||
| 		err = gtserror.Newf("block exists between accounts %s and %s", requester.ID, receiver.ID) | 		const text = "block exists between accounts" | ||||||
| 		return nil, nil, gtserror.NewErrorForbidden(err) | 		return nil, nil, gtserror.NewErrorForbidden(errors.New(text)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return requester, receiver, nil | 	return requester, receiver, nil | ||||||
|  |  | ||||||
|  | @ -35,7 +35,6 @@ import ( | ||||||
| // StatusGet handles the getting of a fedi/activitypub representation of a local status. | // StatusGet handles the getting of a fedi/activitypub representation of a local status. | ||||||
| // It performs appropriate authentication before returning a JSON serializable interface. | // It performs appropriate authentication before returning a JSON serializable interface. | ||||||
| func (p *Processor) StatusGet(ctx context.Context, requestedUser string, statusID string) (interface{}, gtserror.WithCode) { | func (p *Processor) StatusGet(ctx context.Context, requestedUser string, statusID string) (interface{}, gtserror.WithCode) { | ||||||
| 	// Authenticate using http signature. |  | ||||||
| 	// Authenticate the incoming request, getting related user accounts. | 	// Authenticate the incoming request, getting related user accounts. | ||||||
| 	requester, receiver, errWithCode := p.authenticate(ctx, requestedUser) | 	requester, receiver, errWithCode := p.authenticate(ctx, requestedUser) | ||||||
| 	if errWithCode != nil { | 	if errWithCode != nil { | ||||||
|  |  | ||||||
|  | @ -26,7 +26,6 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/activity/streams/vocab" | 	"github.com/superseriousbusiness/activity/streams/vocab" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/uris" | 	"github.com/superseriousbusiness/gotosocial/internal/uris" | ||||||
| ) | ) | ||||||
|  | @ -35,7 +34,7 @@ import ( | ||||||
| // performing authentication before returning a JSON serializable interface to the caller. | // performing authentication before returning a JSON serializable interface to the caller. | ||||||
| func (p *Processor) UserGet(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { | func (p *Processor) UserGet(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) { | ||||||
| 	// (Try to) get the requested local account from the db. | 	// (Try to) get the requested local account from the db. | ||||||
| 	requestedAccount, err := p.state.DB.GetAccountByUsernameDomain(ctx, requestedUsername, "") | 	receiver, err := p.state.DB.GetAccountByUsernameDomain(ctx, requestedUsername, "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, db.ErrNoEntries) { | 		if errors.Is(err, db.ErrNoEntries) { | ||||||
| 			// Account just not found w/ this username. | 			// Account just not found w/ this username. | ||||||
|  | @ -54,8 +53,9 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque | ||||||
| 		// the bare minimum user profile needed for the pubkey. | 		// the bare minimum user profile needed for the pubkey. | ||||||
| 		// | 		// | ||||||
| 		// TODO: https://github.com/superseriousbusiness/gotosocial/issues/1186 | 		// TODO: https://github.com/superseriousbusiness/gotosocial/issues/1186 | ||||||
| 		minimalPerson, err := p.converter.AccountToASMinimal(ctx, requestedAccount) | 		minimalPerson, err := p.converter.AccountToASMinimal(ctx, receiver) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | 			err := gtserror.Newf("error converting to minimal account: %w", err) | ||||||
| 			return nil, gtserror.NewErrorInternalError(err) | 			return nil, gtserror.NewErrorInternalError(err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -72,11 +72,13 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Auth passed, generate the proper AP representation. | 	// Auth passed, generate the proper AP representation. | ||||||
| 	person, err := p.converter.AccountToAS(ctx, requestedAccount) | 	person, err := p.converter.AccountToAS(ctx, receiver) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		err := gtserror.Newf("error converting account: %w", err) | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if pubKeyAuth.Handshaking { | ||||||
| 		// If we are currently handshaking with the remote account | 		// If we are currently handshaking with the remote account | ||||||
| 		// making the request, then don't be coy: just serve the AP | 		// making the request, then don't be coy: just serve the AP | ||||||
| 		// representation of the target account. | 		// representation of the target account. | ||||||
|  | @ -89,39 +91,20 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque | ||||||
| 		// Instead, we end up in an 'I'll show you mine if you show me | 		// Instead, we end up in an 'I'll show you mine if you show me | ||||||
| 		// yours' situation, where we sort of agree to reveal each | 		// yours' situation, where we sort of agree to reveal each | ||||||
| 		// other's profiles at the same time. | 		// other's profiles at the same time. | ||||||
| 	if p.federator.Handshaking(requestedUsername, pubKeyAuth.OwnerURI) { |  | ||||||
| 		return data(person) | 		return data(person) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// We're not currently handshaking with the requestingAccountURI, | 	// Get requester from auth. | ||||||
| 	// so fetch its details and ensure it's up to date + not blocked. | 	requester := pubKeyAuth.Owner | ||||||
| 	requestingAccount, _, err := p.federator.GetAccountByURI( |  | ||||||
| 		// On a hot path so fail quickly. |  | ||||||
| 		gtscontext.SetFastFail(ctx), |  | ||||||
| 		requestedUsername, |  | ||||||
| 		pubKeyAuth.OwnerURI, |  | ||||||
| 	) |  | ||||||
| 	if err != nil { |  | ||||||
| 		err := gtserror.Newf("error getting account %s: %w", pubKeyAuth.OwnerURI, err) |  | ||||||
| 		return nil, gtserror.NewErrorUnauthorized(err) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if !requestingAccount.SuspendedAt.IsZero() { | 	// Check that block does not exist between receiver and requester. | ||||||
| 		// Account was marked as suspended by a | 	blocked, err := p.state.DB.IsBlocked(ctx, receiver.ID, requester.ID) | ||||||
| 		// local admin action. Stop request early. |  | ||||||
| 		err = fmt.Errorf("account %s marked as suspended", requestingAccount.ID) |  | ||||||
| 		return nil, gtserror.NewErrorForbidden(err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	blocked, err := p.state.DB.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err := gtserror.Newf("error checking block from account %s to account %s: %w", requestedAccount.ID, requestingAccount.ID, err) | 		err := gtserror.Newf("error checking block: %w", err) | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} else if blocked { | ||||||
| 
 | 		const text = "block exists between accounts" | ||||||
| 	if blocked { | 		return nil, gtserror.NewErrorForbidden(errors.New(text)) | ||||||
| 		err := fmt.Errorf("account %s blocks account %s", requestedAccount.ID, requestingAccount.ID) |  | ||||||
| 		return nil, gtserror.NewErrorForbidden(err) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return data(person) | 	return data(person) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue