Pass join errors through internal API boundaries (#1157)

* Pass join errors through internal API boundaries

Required for certain invite sytests. We will need to think of a
better way of handling this going forwards.

* Include m.room.avatar in stripped state; handle trailing slashes when GETing state events

* Update whitelist

* Update whitelist
This commit is contained in:
Kegsay 2020-06-24 09:59:59 +01:00 committed by GitHub
parent 1f93427ed9
commit 0577bfca55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 66 additions and 6 deletions

View file

@ -15,8 +15,10 @@
package routing package routing
import ( import (
"errors"
"net/http" "net/http"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/httputil"
"github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/jsonerror"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
@ -52,7 +54,8 @@ func JoinRoomByIDOrAlias(
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed") util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
} else { } else {
// Request our profile content to populate the request content with. // 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 { if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetProfileByLocalpart failed") util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetProfileByLocalpart failed")
} else { } else {
@ -62,11 +65,32 @@ func JoinRoomByIDOrAlias(
} }
// Ask the roomserver to perform the join. // 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{ return util.JSONResponse{
Code: http.StatusBadRequest, 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{ return util.JSONResponse{

View file

@ -159,13 +159,18 @@ func Setup(
return OnIncomingStateRequest(req.Context(), rsAPI, vars["roomID"]) return OnIncomingStateRequest(req.Context(), rsAPI, vars["roomID"])
})).Methods(http.MethodGet, http.MethodOptions) })).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)) vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil { if err != nil {
return util.ErrorResponse(err) 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" 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) })).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 { r0mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *api.Device) util.JSONResponse {

View file

@ -5,6 +5,17 @@ import (
"github.com/matrix-org/util" "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 { type PerformJoinRequest struct {
RoomIDOrAlias string `json:"room_id_or_alias"` RoomIDOrAlias string `json:"room_id_or_alias"`
UserID string `json:"user_id"` UserID string `json:"user_id"`
@ -13,7 +24,12 @@ type PerformJoinRequest struct {
} }
type PerformJoinResponse struct { type PerformJoinResponse struct {
// The room ID, populated on success.
RoomID string `json:"room_id"` 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 { type PerformLeaveRequest struct {

View file

@ -298,9 +298,12 @@ func buildInviteStrippedState(
return nil, fmt.Errorf("room %q unknown", input.Event.RoomID()) return nil, fmt.Errorf("room %q unknown", input.Event.RoomID())
} }
stateWanted := []gomatrixserverlib.StateKeyTuple{} 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{ for _, t := range []string{
gomatrixserverlib.MRoomName, gomatrixserverlib.MRoomCanonicalAlias, gomatrixserverlib.MRoomName, gomatrixserverlib.MRoomCanonicalAlias,
gomatrixserverlib.MRoomAliases, gomatrixserverlib.MRoomJoinRules, gomatrixserverlib.MRoomAliases, gomatrixserverlib.MRoomJoinRules,
"m.room.avatar",
} { } {
stateWanted = append(stateWanted, gomatrixserverlib.StateKeyTuple{ stateWanted = append(stateWanted, gomatrixserverlib.StateKeyTuple{
EventType: t, EventType: t,

View file

@ -2,6 +2,7 @@ package internal
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -21,9 +22,11 @@ func (r *RoomserverInternalAPI) PerformJoin(
) error { ) error {
_, domain, err := gomatrixserverlib.SplitID('@', req.UserID) _, domain, err := gomatrixserverlib.SplitID('@', req.UserID)
if err != nil { if err != nil {
res.Error = api.JoinErrorBadRequest
return fmt.Errorf("Supplied user ID %q in incorrect format", req.UserID) return fmt.Errorf("Supplied user ID %q in incorrect format", req.UserID)
} }
if domain != r.Cfg.Matrix.ServerName { if domain != r.Cfg.Matrix.ServerName {
res.Error = api.JoinErrorBadRequest
return fmt.Errorf("User %q does not belong to this homeserver", req.UserID) return fmt.Errorf("User %q does not belong to this homeserver", req.UserID)
} }
if strings.HasPrefix(req.RoomIDOrAlias, "!") { if strings.HasPrefix(req.RoomIDOrAlias, "!") {
@ -32,6 +35,7 @@ func (r *RoomserverInternalAPI) PerformJoin(
if strings.HasPrefix(req.RoomIDOrAlias, "#") { if strings.HasPrefix(req.RoomIDOrAlias, "#") {
return r.performJoinRoomByAlias(ctx, req, res) return r.performJoinRoomByAlias(ctx, req, res)
} }
res.Error = api.JoinErrorBadRequest
return fmt.Errorf("Room ID or alias %q is invalid", req.RoomIDOrAlias) 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. // Get the domain part of the room ID.
_, domain, err := gomatrixserverlib.SplitID('!', req.RoomIDOrAlias) _, domain, err := gomatrixserverlib.SplitID('!', req.RoomIDOrAlias)
if err != nil { if err != nil {
res.Error = api.JoinErrorBadRequest
return fmt.Errorf("Room ID %q is invalid", req.RoomIDOrAlias) return fmt.Errorf("Room ID %q is invalid", req.RoomIDOrAlias)
} }
req.ServerNames = append(req.ServerNames, domain) req.ServerNames = append(req.ServerNames, domain)
@ -198,6 +203,10 @@ func (r *RoomserverInternalAPI) performJoinRoomByID(
} }
inputRes := api.InputRoomEventsResponse{} inputRes := api.InputRoomEventsResponse{}
if err = r.InputRoomEvents(ctx, &inputReq, &inputRes); err != nil { if err = r.InputRoomEvents(ctx, &inputReq, &inputRes); err != nil {
var notAllowed *gomatrixserverlib.NotAllowed
if errors.As(err, &notAllowed) {
res.Error = api.JoinErrorNotAllowed
}
return fmt.Errorf("r.InputRoomEvents: %w", err) 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 // room. If it is then there's nothing more to do - the room just
// hasn't been created yet. // hasn't been created yet.
if domain == r.Cfg.Matrix.ServerName { if domain == r.Cfg.Matrix.ServerName {
res.Error = api.JoinErrorNoRoom
return fmt.Errorf("Room ID %q does not exist", req.RoomIDOrAlias) return fmt.Errorf("Room ID %q does not exist", req.RoomIDOrAlias)
} }

View file

@ -34,7 +34,7 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) {
return util.MessageResponse(http.StatusBadRequest, err.Error()) return util.MessageResponse(http.StatusBadRequest, err.Error())
} }
if err := r.PerformJoin(req.Context(), &request, &response); err != nil { 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} return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}), }),

View file

@ -62,6 +62,8 @@ Request to logout with invalid an access token is rejected
Request to logout without 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.create to myself
Room creation reports m.room.member 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 # Blacklisted because these tests call /r0/events which we don't implement
# New room members see their own join event # New room members see their own join event
# Existing members see new members' join events # Existing members see new members' join events