Initial move of make join

This commit is contained in:
Devon Hudson 2023-05-08 16:22:01 -06:00
parent a49c9f01e2
commit c02d947c8c
No known key found for this signature in database
GPG key ID: CD06B18E77F6A628
4 changed files with 182 additions and 189 deletions

View file

@ -15,6 +15,7 @@
package routing
import (
"context"
"encoding/json"
"fmt"
"net/http"
@ -34,153 +35,144 @@ import (
"github.com/matrix-org/dendrite/setup/config"
)
type RoomQuerier struct {
roomserver api.FederationRoomserverAPI
}
func (rq *RoomQuerier) RoomInfo(ctx context.Context, roomID *spec.RoomID) (*gomatrixserverlib.RoomInfo, error) {
roomInfo, err := rq.roomserver.QueryRoomInfo(ctx, roomID)
var result *gomatrixserverlib.RoomInfo
if roomInfo != nil && !roomInfo.IsStub() {
result = &gomatrixserverlib.RoomInfo{
Version: roomInfo.RoomVersion,
NID: int64(roomInfo.RoomNID),
}
}
return result, err
}
func (rq *RoomQuerier) StateEvent(ctx context.Context, roomID *spec.RoomID, eventType spec.MatrixEventType, stateKey string) (gomatrixserverlib.PDU, error) {
return rq.roomserver.GetStateEvent(ctx, roomID, eventType, stateKey)
}
func (rq *RoomQuerier) ServerInRoom(ctx context.Context, server spec.ServerName, roomID *spec.RoomID) (*gomatrixserverlib.JoinedToRoomResponse, error) {
req := api.QueryServerJoinedToRoomRequest{
ServerName: server,
RoomID: roomID.String(),
}
res := api.QueryServerJoinedToRoomResponse{}
if err := rq.roomserver.QueryServerJoinedToRoom(ctx, &req, &res); err != nil {
util.GetLogger(ctx).WithError(err).Error("rsAPI.QueryServerJoinedToRoom failed")
return nil, fmt.Errorf("InternalServerError: Failed to query room")
}
joinedResponse := gomatrixserverlib.JoinedToRoomResponse{
RoomExists: res.RoomExists,
ServerInRoom: res.IsInRoom,
}
return &joinedResponse, nil
}
func (rq *RoomQuerier) Membership(ctx context.Context, roomNID int64, userID *spec.UserID) (bool, error) {
return rq.roomserver.IsInRoom(ctx, types.RoomNID(roomNID), userID)
}
func (rq *RoomQuerier) GetJoinedUsers(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomNID int64) ([]gomatrixserverlib.PDU, error) {
return rq.roomserver.GetLocallyJoinedUsers(ctx, roomVersion, types.RoomNID(roomNID))
}
func (rq *RoomQuerier) InvitePending(ctx context.Context, roomID *spec.RoomID, userID *spec.UserID) (bool, error) {
return rq.roomserver.IsInvitePending(ctx, roomID, userID)
}
// MakeJoin implements the /make_join API
func MakeJoin(
httpReq *http.Request,
request *fclient.FederationRequest,
cfg *config.FederationAPI,
rsAPI api.FederationRoomserverAPI,
roomID, userID string,
roomID *spec.RoomID, userID *spec.UserID,
remoteVersions []gomatrixserverlib.RoomVersion,
) util.JSONResponse {
roomVersion, err := rsAPI.QueryRoomVersionForRoom(httpReq.Context(), roomID)
roomVersion, err := rsAPI.QueryRoomVersionForRoom(httpReq.Context(), roomID.String())
if err != nil {
util.GetLogger(httpReq.Context()).WithError(err).Error("failed obtaining room version")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: jsonerror.InternalServerError(),
}
}
// Check that the room that the remote side is trying to join is actually
// one of the room versions that they listed in their supported ?ver= in
// the make_join URL.
// https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-make-join-roomid-userid
remoteSupportsVersion := false
for _, v := range remoteVersions {
if v == roomVersion {
remoteSupportsVersion = true
break
createJoinTemplate := func(proto *gomatrixserverlib.ProtoEvent) (gomatrixserverlib.PDU, []gomatrixserverlib.PDU, *util.JSONResponse) {
identity, err := cfg.Matrix.SigningIdentityFor(request.Destination())
if err != nil {
return nil, nil, &util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound(
fmt.Sprintf("Server name %q does not exist", request.Destination()),
),
}
}
}
// If it isn't, stop trying to join the room.
if !remoteSupportsVersion {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.IncompatibleRoomVersion(roomVersion),
queryRes := api.QueryLatestEventsAndStateResponse{
RoomVersion: roomVersion,
}
event, err := eventutil.QueryAndBuildEvent(httpReq.Context(), proto, cfg.Matrix, identity, time.Now(), rsAPI, &queryRes)
if err == eventutil.ErrRoomNoExists {
return nil, nil, &util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound("Room does not exist"),
}
} else if e, ok := err.(gomatrixserverlib.BadJSONError); ok {
return nil, nil, &util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON(e.Error()),
}
} else if err != nil {
util.GetLogger(httpReq.Context()).WithError(err).Error("eventutil.BuildEvent failed")
e := jsonerror.InternalServerError()
return nil, nil, &e
}
stateEvents := make([]gomatrixserverlib.PDU, len(queryRes.StateEvents))
for i, stateEvent := range queryRes.StateEvents {
stateEvents[i] = stateEvent.PDU
}
return event, stateEvents, nil
}
_, domain, err := gomatrixserverlib.SplitID('@', userID)
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("Invalid UserID"),
}
}
if domain != request.Origin() {
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: jsonerror.Forbidden("The join must be sent by the server of the user"),
}
roomQuerier := RoomQuerier{
roomserver: rsAPI,
}
// Check if we think we are still joined to the room
inRoomReq := &api.QueryServerJoinedToRoomRequest{
ServerName: cfg.Matrix.ServerName,
RoomID: roomID,
input := gomatrixserverlib.HandleMakeJoinInput{
Context: httpReq.Context(),
UserID: userID,
RoomID: roomID,
RoomVersion: roomVersion,
RemoteVersions: remoteVersions,
RequestOrigin: request.Origin(),
RequestDestination: request.Destination(),
LocalServerName: cfg.Matrix.ServerName,
RoomQuerier: &roomQuerier,
BuildEventTemplate: createJoinTemplate,
}
inRoomRes := &api.QueryServerJoinedToRoomResponse{}
if err = rsAPI.QueryServerJoinedToRoom(httpReq.Context(), inRoomReq, inRoomRes); err != nil {
util.GetLogger(httpReq.Context()).WithError(err).Error("rsAPI.QueryServerJoinedToRoom failed")
response, internalErr := gomatrixserverlib.HandleMakeJoin(input)
if internalErr != nil {
return *internalErr
}
if response == nil {
util.GetLogger(httpReq.Context()).Error("gmsl.HandleMakeJoin returned invalid response")
return jsonerror.InternalServerError()
}
if !inRoomRes.RoomExists {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound(fmt.Sprintf("Room ID %q was not found on this server", roomID)),
}
}
if !inRoomRes.IsInRoom {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound(fmt.Sprintf("Room ID %q has no remaining users on this server", roomID)),
}
}
// Check if the restricted join is allowed. If the room doesn't
// support restricted joins then this is effectively a no-op.
res, authorisedVia, err := checkRestrictedJoin(httpReq, rsAPI, roomVersion, roomID, userID)
if err != nil {
util.GetLogger(httpReq.Context()).WithError(err).Error("checkRestrictedJoin failed")
return jsonerror.InternalServerError()
} else if res != nil {
return *res
}
// Try building an event for the server
proto := gomatrixserverlib.ProtoEvent{
Sender: userID,
RoomID: roomID,
Type: "m.room.member",
StateKey: &userID,
}
content := gomatrixserverlib.MemberContent{
Membership: spec.Join,
AuthorisedVia: authorisedVia,
}
if err = proto.SetContent(content); err != nil {
util.GetLogger(httpReq.Context()).WithError(err).Error("builder.SetContent failed")
return jsonerror.InternalServerError()
}
identity, err := cfg.Matrix.SigningIdentityFor(request.Destination())
if err != nil {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound(
fmt.Sprintf("Server name %q does not exist", request.Destination()),
),
}
}
queryRes := api.QueryLatestEventsAndStateResponse{
RoomVersion: roomVersion,
}
event, err := eventutil.QueryAndBuildEvent(httpReq.Context(), &proto, cfg.Matrix, identity, time.Now(), rsAPI, &queryRes)
if err == eventutil.ErrRoomNoExists {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound("Room does not exist"),
}
} else if e, ok := err.(gomatrixserverlib.BadJSONError); ok {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON(e.Error()),
}
} else if err != nil {
util.GetLogger(httpReq.Context()).WithError(err).Error("eventutil.BuildEvent failed")
return jsonerror.InternalServerError()
}
// Check that the join is allowed or not
stateEvents := make([]gomatrixserverlib.PDU, len(queryRes.StateEvents))
for i := range queryRes.StateEvents {
stateEvents[i] = queryRes.StateEvents[i].PDU
}
provider := gomatrixserverlib.NewAuthEvents(stateEvents)
if err = gomatrixserverlib.Allowed(event.PDU, &provider); err != nil {
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: jsonerror.Forbidden(err.Error()),
}
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: map[string]interface{}{
"event": proto,
"room_version": roomVersion,
"event": response.JoinTemplateEvent,
"room_version": response.RoomVersion,
},
}
}
@ -452,74 +444,6 @@ func SendJoin(
}
}
// checkRestrictedJoin finds out whether or not we can assist in processing
// a restricted room join. If the room version does not support restricted
// joins then this function returns with no side effects. This returns three
// values:
// - an optional JSON response body (i.e. M_UNABLE_TO_AUTHORISE_JOIN) which
// should always be sent back to the client if one is specified
// - a user ID of an authorising user, typically a user that has power to
// issue invites in the room, if one has been found
// - an error if there was a problem finding out if this was allowable,
// like if the room version isn't known or a problem happened talking to
// the roomserver
func checkRestrictedJoin(
httpReq *http.Request,
rsAPI api.FederationRoomserverAPI,
roomVersion gomatrixserverlib.RoomVersion,
roomID, userID string,
) (*util.JSONResponse, string, error) {
verImpl, err := gomatrixserverlib.GetRoomVersion(roomVersion)
if err != nil {
return nil, "", err
}
if !verImpl.MayAllowRestrictedJoinsInEventAuth() {
return nil, "", nil
}
req := &api.QueryRestrictedJoinAllowedRequest{
RoomID: roomID,
UserID: userID,
}
res := &api.QueryRestrictedJoinAllowedResponse{}
if err := rsAPI.QueryRestrictedJoinAllowed(httpReq.Context(), req, res); err != nil {
return nil, "", err
}
switch {
case !res.Restricted:
// The join rules for the room don't restrict membership.
return nil, "", nil
case !res.Resident:
// The join rules restrict membership but our server isn't currently
// joined to all of the allowed rooms, so we can't actually decide
// whether or not to allow the user to join. This error code should
// tell the joining server to try joining via another resident server
// instead.
return &util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.UnableToAuthoriseJoin("This server cannot authorise the join."),
}, "", nil
case !res.Allowed:
// The join rules restrict membership, our server is in the relevant
// rooms and the user wasn't joined to join any of the allowed rooms
// and therefore can't join this room.
return &util.JSONResponse{
Code: http.StatusForbidden,
JSON: jsonerror.Forbidden("You are not joined to any matching rooms."),
}, "", nil
default:
// The join rules restrict membership, our server is in the relevant
// rooms and the user was allowed to join because they belong to one
// of the allowed rooms. We now need to pick one of our own local users
// from within the room to use as the authorising user ID, so that it
// can be referred to from within the membership content.
return nil, res.AuthorisedVia, nil
}
}
type eventsByDepth []*types.HeaderedEvent
func (e eventsByDepth) Len() int {

View file

@ -313,8 +313,6 @@ func Setup(
JSON: jsonerror.Forbidden("Forbidden by server ACLs"),
}
}
roomID := vars["roomID"]
userID := vars["userID"]
queryVars := httpReq.URL.Query()
remoteVersions := []gomatrixserverlib.RoomVersion{}
if vers, ok := queryVars["ver"]; ok {
@ -329,6 +327,24 @@ func Setup(
// https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-make-join-roomid-userid
remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersionV1)
}
userID, err := spec.NewUserID(vars["userID"], true)
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("Invalid UserID"),
}
}
roomID, err := spec.NewRoomID(vars["roomID"])
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("Invalid RoomID"),
}
}
logrus.Warnf("Processing make_join for user %s, room %s", userID.String(), roomID.String())
return MakeJoin(
httpReq, request, cfg, rsAPI, roomID, userID, remoteVersions,
)

View file

@ -8,6 +8,7 @@ import (
asAPI "github.com/matrix-org/dendrite/appservice/api"
fsAPI "github.com/matrix-org/dendrite/federationapi/api"
"github.com/matrix-org/dendrite/roomserver/types"
userapi "github.com/matrix-org/dendrite/userapi/api"
)
@ -224,6 +225,12 @@ type FederationRoomserverAPI interface {
PerformInvite(ctx context.Context, req *PerformInviteRequest) error
// Query a given amount (or less) of events prior to a given set of events.
PerformBackfill(ctx context.Context, req *PerformBackfillRequest, res *PerformBackfillResponse) error
IsInvitePending(ctx context.Context, roomID *spec.RoomID, userID *spec.UserID) (bool, error)
QueryRoomInfo(ctx context.Context, roomID *spec.RoomID) (*types.RoomInfo, error)
GetStateEvent(ctx context.Context, roomID *spec.RoomID, eventType spec.MatrixEventType, stateKey string) (gomatrixserverlib.PDU, error)
IsInRoom(ctx context.Context, roomID types.RoomNID, userID *spec.UserID) (bool, error)
GetLocallyJoinedUsers(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomNID types.RoomNID) ([]gomatrixserverlib.PDU, error)
}
type KeyserverRoomserverAPI interface {

View file

@ -858,6 +858,52 @@ func (r *Queryer) QueryAuthChain(ctx context.Context, req *api.QueryAuthChainReq
return nil
}
func (r *Queryer) IsInvitePending(ctx context.Context, roomID *spec.RoomID, userID *spec.UserID) (bool, error) {
pending, _, _, _, err := helpers.IsInvitePending(ctx, r.DB, roomID.String(), userID.String())
return pending, err
}
func (r *Queryer) QueryRoomInfo(ctx context.Context, roomID *spec.RoomID) (*types.RoomInfo, error) {
return r.DB.RoomInfo(ctx, roomID.String())
}
func (r *Queryer) GetStateEvent(ctx context.Context, roomID *spec.RoomID, eventType spec.MatrixEventType, stateKey string) (gomatrixserverlib.PDU, error) {
return r.DB.GetStateEvent(ctx, roomID.String(), string(eventType), "")
}
func (r *Queryer) IsInRoom(ctx context.Context, roomNID types.RoomNID, userID *spec.UserID) (bool, error) {
_, isIn, _, err := r.DB.GetMembership(ctx, roomNID, userID.String())
return isIn, err
}
func (r *Queryer) GetLocallyJoinedUsers(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomNID types.RoomNID) ([]gomatrixserverlib.PDU, error) {
joinNIDs, err := r.DB.GetMembershipEventNIDsForRoom(ctx, roomNID, true, true)
if err != nil || len(joinNIDs) == 0 {
// There should always be more than one join NID at this point
// because we are gated behind GetLocalServerInRoom, but y'know,
// sometimes strange things happen.
return nil, err
}
// For each of the joined users, let's see if we can get a valid
// membership event.
joinedUsers := []gomatrixserverlib.PDU{}
for _, joinNID := range joinNIDs {
events, err := r.DB.Events(ctx, roomVersion, []types.EventNID{joinNID})
if err != nil || len(events) != 1 {
continue
}
event := events[0]
if event.Type() != spec.MRoomMember || event.StateKey() == nil {
continue // shouldn't happen
}
joinedUsers = append(joinedUsers, event)
}
return joinedUsers, nil
}
// nolint:gocyclo
func (r *Queryer) QueryRestrictedJoinAllowed(ctx context.Context, req *api.QueryRestrictedJoinAllowedRequest, res *api.QueryRestrictedJoinAllowedResponse) error {
// Look up if we know anything about the room. If it doesn't exist