Skip to content

Commit

Permalink
Implement MSC3916 (#3397)
Browse files Browse the repository at this point in the history
  • Loading branch information
S7evinK authored and neilalexander committed Aug 24, 2024
1 parent 2f9cfc5 commit 51b5c98
Show file tree
Hide file tree
Showing 13 changed files with 375 additions and 73 deletions.
3 changes: 2 additions & 1 deletion clientapi/routing/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func Setup(
unstableFeatures := map[string]bool{
"org.matrix.e2e_cross_signing": true,
"org.matrix.msc2285.stable": true,
"org.matrix.msc3916.stable": true,
}
for _, msc := range cfg.MSCs.MSCs {
unstableFeatures["org.matrix."+msc] = true
Expand Down Expand Up @@ -707,7 +708,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)

v3mux.Handle("/auth/{authType}/fallback/web",
httputil.MakeHTMLAPI("auth_fallback", enableMetrics, func(w http.ResponseWriter, req *http.Request) {
httputil.MakeHTTPAPI("auth_fallback", userAPI, enableMetrics, func(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
AuthFallback(w, req, vars["authType"], cfg)
}),
Expand Down
31 changes: 31 additions & 0 deletions federationapi/routing/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package routing

import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
Expand Down Expand Up @@ -601,6 +602,36 @@ func MakeFedAPI(
return httputil.MakeExternalAPI(metricsName, h)
}

// MakeFedHTTPAPI makes an http.Handler that checks matrix federation authentication.
func MakeFedHTTPAPI(
serverName spec.ServerName,
isLocalServerName func(spec.ServerName) bool,
keyRing gomatrixserverlib.JSONVerifier,
f func(http.ResponseWriter, *http.Request),
) http.Handler {
h := func(w http.ResponseWriter, req *http.Request) {
fedReq, errResp := fclient.VerifyHTTPRequest(
req, time.Now(), serverName, isLocalServerName, keyRing,
)

enc := json.NewEncoder(w)
logger := util.GetLogger(req.Context())
if fedReq == nil {

logger.Debugf("VerifyUserFromRequest %s -> HTTP %d", req.RemoteAddr, errResp.Code)
w.WriteHeader(errResp.Code)
if err := enc.Encode(errResp); err != nil {
logger.WithError(err).Error("failed to encode JSON response")
}
return
}

f(w, req)
}

return http.HandlerFunc(h)
}

type FederationWakeups struct {
FsAPI *fedInternal.FederationInternalAPI
origins sync.Map
Expand Down
61 changes: 31 additions & 30 deletions internal/gomatrixserverlib/fclient/federationclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ type FederationClient interface {
ctx context.Context, origin, s spec.ServerName, userID string, field string,
) (res RespProfile, err error)

P2PSendTransactionToRelay(ctx context.Context, u spec.UserID, t gomatrixserverlib.Transaction, forwardingServer spec.ServerName) (res EmptyResp, err error)
P2PGetTransactionFromRelay(ctx context.Context, u spec.UserID, prev RelayEntry, relayServer spec.ServerName) (res RespGetRelayTransaction, err error)
DownloadMedia(ctx context.Context, origin, destination spec.ServerName, mediaID string) (res *http.Response, err error)
}

// A FederationClient is a matrix federation client that adds
Expand Down Expand Up @@ -142,34 +141,6 @@ func (ac *federationClient) SendTransaction(
return
}

// P2PSendTransactionToRelay sends a transaction for forwarding to the destination.
func (ac *federationClient) P2PSendTransactionToRelay(
ctx context.Context, u spec.UserID, t gomatrixserverlib.Transaction, forwardingServer spec.ServerName,
) (res EmptyResp, err error) {
path := federationPathPrefixV1 + "/send_relay/" +
string(t.TransactionID) + "/" +
url.PathEscape(u.String())
req := NewFederationRequest("PUT", t.Origin, forwardingServer, path)
if err = req.SetContent(t); err != nil {
return
}
err = ac.doRequest(ctx, req, &res)
return
}

// P2PGetTransactionFromRelay requests a transaction from a relay destined for this server.
func (ac *federationClient) P2PGetTransactionFromRelay(
ctx context.Context, u spec.UserID, prev RelayEntry, relayServer spec.ServerName,
) (res RespGetRelayTransaction, err error) {
path := federationPathPrefixV1 + "/relay_txn/" + url.PathEscape(u.String())
req := NewFederationRequest("GET", u.Domain(), relayServer, path)
if err = req.SetContent(prev); err != nil {
return
}
err = ac.doRequest(ctx, req, &res)
return
}

// Creates a version query string with all the specified room versions, typically
// the list of all supported room versions.
// Needed when making a /make_knock or /make_join request.
Expand Down Expand Up @@ -736,3 +707,33 @@ func (ac *federationClient) RoomHierarchy(
}
return
}

// DownloadMedia performs an authenticated federation request for a given mediaID.
// The caller is responsible to close the returned response body.
func (ac *federationClient) DownloadMedia(
ctx context.Context, origin, destination spec.ServerName, mediaID string,
) (*http.Response, error) {
var identity *SigningIdentity
for _, id := range ac.identities {
if id.ServerName == origin {
identity = id
break
}
}
if identity == nil {
return nil, fmt.Errorf("no signing identity for server name %q", origin)
}

path := federationPathPrefixV1 + "/media/download/" + url.PathEscape(mediaID)
req := NewFederationRequest("GET", origin, destination, path)

if err := req.Sign(identity.ServerName, identity.KeyID, identity.PrivateKey); err != nil {
return nil, err
}

httpReq, err := req.HTTPRequest()
if err != nil {
return nil, err
}
return ac.DoHTTPRequest(ctx, httpReq)
}
2 changes: 2 additions & 0 deletions internal/gomatrixserverlib/fclient/federationtypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ type PublicRoom struct {
GuestCanJoin bool `json:"guest_can_join"`
// The URL for the room's avatar, if one is set.
AvatarURL string `json:"avatar_url,omitempty"`
// The join rule for this room
JoinRule string `json:"join_rule,omitempty"`
}

// A RespEventAuth is the content of a response to GET /_matrix/federation/v1/event_auth/{roomID}/{eventID}
Expand Down
30 changes: 28 additions & 2 deletions internal/httputil/httpapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package httputil

import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
Expand All @@ -41,6 +42,7 @@ type BasicAuth struct {

type AuthAPIOpts struct {
GuestAccessAllowed bool
WithAuth bool
}

// AuthAPIOption is an option to MakeAuthAPI to add additional checks (e.g. guest access) to verify
Expand All @@ -54,6 +56,13 @@ func WithAllowGuests() AuthAPIOption {
}
}

// WithAuth is an option to MakeHTTPAPI to add authentication.
func WithAuth() AuthAPIOption {
return func(opts *AuthAPIOpts) {
opts.WithAuth = true
}
}

// MakeAuthAPI turns a util.JSONRequestHandler function into an http.Handler which authenticates the request.
func MakeAuthAPI(
metricsName string, userAPI userapi.QueryAcccessTokenAPI,
Expand Down Expand Up @@ -170,10 +179,27 @@ func MakeExternalAPI(metricsName string, f func(*http.Request) util.JSONResponse
return http.HandlerFunc(withSpan)
}

// MakeHTMLAPI adds Span metrics to the HTML Handler function
// MakeHTTPAPI adds Span metrics to the HTML Handler function
// This is used to serve HTML alongside JSON error messages
func MakeHTMLAPI(metricsName string, enableMetrics bool, f func(http.ResponseWriter, *http.Request)) http.Handler {
func MakeHTTPAPI(metricsName string, userAPI userapi.QueryAcccessTokenAPI, enableMetrics bool, f func(http.ResponseWriter, *http.Request), checks ...AuthAPIOption) http.Handler {
withSpan := func(w http.ResponseWriter, req *http.Request) {
// apply additional checks, if any
opts := AuthAPIOpts{}
for _, opt := range checks {
opt(&opts)
}

if opts.WithAuth {
logger := util.GetLogger(req.Context())
_, jsonErr := auth.VerifyUserFromRequest(req, userAPI)
if jsonErr != nil {
w.WriteHeader(jsonErr.Code)
if err := json.NewEncoder(w).Encode(jsonErr.JSON); err != nil {
logger.WithError(err).Error("failed to encode JSON response")
}
return
}
}
f(w, req)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/sqlutil/sqlutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,5 +218,5 @@ func assertNoError(t *testing.T, err error, msg string) {
if err == nil {
return
}
t.Fatalf(msg)
t.Fatal(msg)
}
9 changes: 6 additions & 3 deletions mediaapi/mediaapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
package mediaapi

import (
"github.com/gorilla/mux"
"github.com/neilalexander/harmony/internal/gomatrixserverlib"
"github.com/neilalexander/harmony/internal/gomatrixserverlib/fclient"
"github.com/neilalexander/harmony/internal/httputil"
"github.com/neilalexander/harmony/internal/sqlutil"
"github.com/neilalexander/harmony/mediaapi/routing"
"github.com/neilalexander/harmony/mediaapi/storage"
Expand All @@ -27,18 +28,20 @@ import (

// AddPublicRoutes sets up and registers HTTP handlers for the MediaAPI component.
func AddPublicRoutes(
mediaRouter *mux.Router,
routers httputil.Routers,
cm *sqlutil.Connections,
cfg *config.Dendrite,
userAPI userapi.MediaUserAPI,
client *fclient.Client,
fedClient fclient.FederationClient,
keyRing gomatrixserverlib.JSONVerifier,
) {
mediaDB, err := storage.NewMediaAPIDatasource(cm, &cfg.MediaAPI.Database)
if err != nil {
logrus.WithError(err).Panicf("failed to connect to media db")
}

routing.Setup(
mediaRouter, cfg, mediaDB, userAPI, client,
routers, cfg, mediaDB, userAPI, client, fedClient, keyRing,
)
}
Loading

0 comments on commit 51b5c98

Please sign in to comment.