diff --git a/clientapi/routing/joinroom.go b/clientapi/routing/joinroom.go index db890d03f..e606e35f1 100644 --- a/clientapi/routing/joinroom.go +++ b/clientapi/routing/joinroom.go @@ -15,8 +15,10 @@ package routing import ( + "errors" "net/http" + "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" @@ -52,7 +54,8 @@ func JoinRoomByIDOrAlias( util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed") } else { // Request our profile content to populate the request content with. - profile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart) + var profile *authtypes.Profile + profile, err = accountDB.GetProfileByLocalpart(req.Context(), localpart) if err != nil { util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetProfileByLocalpart failed") } else { @@ -62,11 +65,32 @@ func JoinRoomByIDOrAlias( } // Ask the roomserver to perform the join. - if err := rsAPI.PerformJoin(req.Context(), &joinReq, &joinRes); err != nil { + err = rsAPI.PerformJoin(req.Context(), &joinReq, &joinRes) + // Handle known errors first, if this is 0 then there will be no matches (eg on success) + switch joinRes.Error { + case roomserverAPI.JoinErrorBadRequest: return util.JSONResponse{ Code: http.StatusBadRequest, - JSON: jsonerror.Unknown(err.Error()), + JSON: jsonerror.Unknown(joinRes.ErrMsg), } + case roomserverAPI.JoinErrorNoRoom: + return util.JSONResponse{ + Code: http.StatusNotFound, + JSON: jsonerror.NotFound(joinRes.ErrMsg), + } + case roomserverAPI.JoinErrorNotAllowed: + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden(joinRes.ErrMsg), + } + } + // this is always populated on generic errors + if joinRes.ErrMsg != "" { + return util.ErrorResponse(errors.New(joinRes.ErrMsg)) + } + // this is set on network errors in polylith mode + if err != nil { + return util.ErrorResponse(err) } return util.JSONResponse{ diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index e91b07ac7..825ac50f2 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -159,13 +159,18 @@ func Setup( return OnIncomingStateRequest(req.Context(), rsAPI, vars["roomID"]) })).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/state/{type}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *api.Device) util.JSONResponse { + r0mux.Handle("/rooms/{roomID}/state/{type:[^/]+/?}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *api.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } + // If there's a trailing slash, remove it + eventType := vars["type"] + if strings.HasSuffix(eventType, "/") { + eventType = eventType[:len(eventType)-1] + } eventFormat := req.URL.Query().Get("format") == "event" - return OnIncomingStateTypeRequest(req.Context(), rsAPI, vars["roomID"], vars["type"], "", eventFormat) + return OnIncomingStateTypeRequest(req.Context(), rsAPI, vars["roomID"], eventType, "", eventFormat) })).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *api.Device) util.JSONResponse { diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index 3e5cae1b6..0f5394c94 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -5,6 +5,17 @@ import ( "github.com/matrix-org/util" ) +type JoinError int + +const ( + // JoinErrorNotAllowed means the user is not allowed to join this room (e.g join_rule:invite or banned) + JoinErrorNotAllowed JoinError = 1 + // JoinErrorBadRequest means the request was wrong in some way (invalid user ID, wrong server, etc) + JoinErrorBadRequest JoinError = 2 + // JoinErrorNoRoom means that the room being joined doesn't exist. + JoinErrorNoRoom JoinError = 3 +) + type PerformJoinRequest struct { RoomIDOrAlias string `json:"room_id_or_alias"` UserID string `json:"user_id"` @@ -13,7 +24,12 @@ type PerformJoinRequest struct { } type PerformJoinResponse struct { + // The room ID, populated on success. RoomID string `json:"room_id"` + // The reason why the join failed. Can be blank. + Error JoinError `json:"error"` + // Debugging description of the error. Always present on failure. + ErrMsg string `json:"err_msg"` } type PerformLeaveRequest struct { diff --git a/roomserver/internal/input_events.go b/roomserver/internal/input_events.go index 4487aea02..fe3bdf4b8 100644 --- a/roomserver/internal/input_events.go +++ b/roomserver/internal/input_events.go @@ -298,9 +298,12 @@ func buildInviteStrippedState( return nil, fmt.Errorf("room %q unknown", input.Event.RoomID()) } stateWanted := []gomatrixserverlib.StateKeyTuple{} + // "If they are set on the room, at least the state for m.room.avatar, m.room.canonical_alias, m.room.join_rules, and m.room.name SHOULD be included." + // https://matrix.org/docs/spec/client_server/r0.6.0#m-room-member for _, t := range []string{ gomatrixserverlib.MRoomName, gomatrixserverlib.MRoomCanonicalAlias, gomatrixserverlib.MRoomAliases, gomatrixserverlib.MRoomJoinRules, + "m.room.avatar", } { stateWanted = append(stateWanted, gomatrixserverlib.StateKeyTuple{ EventType: t, diff --git a/roomserver/internal/perform_join.go b/roomserver/internal/perform_join.go index 1c951bb15..44f842404 100644 --- a/roomserver/internal/perform_join.go +++ b/roomserver/internal/perform_join.go @@ -2,6 +2,7 @@ package internal import ( "context" + "errors" "fmt" "strings" "time" @@ -21,9 +22,11 @@ func (r *RoomserverInternalAPI) PerformJoin( ) error { _, domain, err := gomatrixserverlib.SplitID('@', req.UserID) if err != nil { + res.Error = api.JoinErrorBadRequest return fmt.Errorf("Supplied user ID %q in incorrect format", req.UserID) } if domain != r.Cfg.Matrix.ServerName { + res.Error = api.JoinErrorBadRequest return fmt.Errorf("User %q does not belong to this homeserver", req.UserID) } if strings.HasPrefix(req.RoomIDOrAlias, "!") { @@ -32,6 +35,7 @@ func (r *RoomserverInternalAPI) PerformJoin( if strings.HasPrefix(req.RoomIDOrAlias, "#") { return r.performJoinRoomByAlias(ctx, req, res) } + res.Error = api.JoinErrorBadRequest return fmt.Errorf("Room ID or alias %q is invalid", req.RoomIDOrAlias) } @@ -99,6 +103,7 @@ func (r *RoomserverInternalAPI) performJoinRoomByID( // Get the domain part of the room ID. _, domain, err := gomatrixserverlib.SplitID('!', req.RoomIDOrAlias) if err != nil { + res.Error = api.JoinErrorBadRequest return fmt.Errorf("Room ID %q is invalid", req.RoomIDOrAlias) } req.ServerNames = append(req.ServerNames, domain) @@ -198,6 +203,10 @@ func (r *RoomserverInternalAPI) performJoinRoomByID( } inputRes := api.InputRoomEventsResponse{} if err = r.InputRoomEvents(ctx, &inputReq, &inputRes); err != nil { + var notAllowed *gomatrixserverlib.NotAllowed + if errors.As(err, ¬Allowed) { + res.Error = api.JoinErrorNotAllowed + } return fmt.Errorf("r.InputRoomEvents: %w", err) } } @@ -207,6 +216,7 @@ func (r *RoomserverInternalAPI) performJoinRoomByID( // room. If it is then there's nothing more to do - the room just // hasn't been created yet. if domain == r.Cfg.Matrix.ServerName { + res.Error = api.JoinErrorNoRoom return fmt.Errorf("Room ID %q does not exist", req.RoomIDOrAlias) } diff --git a/roomserver/inthttp/server.go b/roomserver/inthttp/server.go index 822acd15b..e3b81daa5 100644 --- a/roomserver/inthttp/server.go +++ b/roomserver/inthttp/server.go @@ -34,7 +34,7 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) { return util.MessageResponse(http.StatusBadRequest, err.Error()) } if err := r.PerformJoin(req.Context(), &request, &response); err != nil { - return util.ErrorResponse(err) + response.ErrMsg = err.Error() } return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), diff --git a/sytest-whitelist b/sytest-whitelist index ef91507be..ceef20ffd 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -62,6 +62,8 @@ Request to logout with invalid an access token is rejected Request to logout without an access token is rejected Room creation reports m.room.create to myself Room creation reports m.room.member to myself +Invited user can see room metadata +Remote invited user can see room metadata # Blacklisted because these tests call /r0/events which we don't implement # New room members see their own join event # Existing members see new members' join events