From 5a2eb5bbb8317a636ffb73b08234fc3145779ce0 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 7 Mar 2017 16:55:02 +0000 Subject: [PATCH] Validate /createRoom JSON --- .../dendrite/clientapi/common/common.go | 31 +++++++++++++- .../dendrite/clientapi/writers/createroom.go | 41 ++++++++++++++----- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/clientapi/common/common.go b/src/github.com/matrix-org/dendrite/clientapi/common/common.go index 72ccfb066..e6e8e8acb 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/common/common.go +++ b/src/github.com/matrix-org/dendrite/clientapi/common/common.go @@ -2,19 +2,48 @@ package common import ( "encoding/json" + "fmt" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/util" "net/http" + "strings" ) +// UserID represents a parsed User ID string +type UserID struct { + Domain string + Localpart string +} + +// UserIDFromString creates a UserID from an input string. Returns an error if +// the string is not a valid user ID. +func UserIDFromString(id string) (uid UserID, err error) { + // https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/types.py#L92 + if len(id) == 0 || id[0] != '@' { + err = fmt.Errorf("user id must start with '@'") + return + } + parts := strings.SplitN(id[1:], ":", 2) + if len(parts) != 2 { + err = fmt.Errorf("user id must be in the form @localpart:domain") + return + } + uid.Localpart = parts[0] + uid.Domain = parts[1] + return +} + // UnmarshalJSONRequest into the given interface pointer. Returns an error JSON response if // there was a problem unmarshalling. Calling this function consumes the request body. func UnmarshalJSONRequest(req *http.Request, iface interface{}) *util.JSONResponse { defer req.Body.Close() if err := json.NewDecoder(req.Body).Decode(iface); err != nil { + // TODO: We may want to suppress the Error() return in production? It's useful when + // debugging because an error will be produced for both invalid/malformed JSON AND + // valid JSON with incorrect types for values. return &util.JSONResponse{ Code: 400, - JSON: jsonerror.NotJSON("The request body was not JSON"), + JSON: jsonerror.BadJSON("The request body could not be decoded into valid JSON. " + err.Error()), } } return nil diff --git a/src/github.com/matrix-org/dendrite/clientapi/writers/createroom.go b/src/github.com/matrix-org/dendrite/clientapi/writers/createroom.go index 6c54e20ae..0f4897174 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/writers/createroom.go +++ b/src/github.com/matrix-org/dendrite/clientapi/writers/createroom.go @@ -4,10 +4,12 @@ import ( "encoding/json" "fmt" "net/http" + "strings" log "github.com/Sirupsen/logrus" "github.com/matrix-org/dendrite/clientapi/auth" "github.com/matrix-org/dendrite/clientapi/common" + "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/util" ) @@ -23,6 +25,26 @@ type createRoomRequest struct { RoomAliasName string `json:"room_alias_name"` } +func (r createRoomRequest) Validate() *util.JSONResponse { + whitespace := "\t\n\x0b\x0c\r " // https://docs.python.org/2/library/string.html#string.whitespace + // https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/handlers/room.py#L81 + if strings.ContainsAny(r.RoomAliasName, whitespace) { + return &util.JSONResponse{ + Code: 400, + JSON: jsonerror.BadJSON("room_alias_name cannot contain whitespace"), + } + } + for _, userID := range r.Invite { + if _, err := common.UserIDFromString(userID); err != nil { + return &util.JSONResponse{ + Code: 400, + JSON: jsonerror.BadJSON("Entries in 'invite' must be valid user IDs"), + } + } + } + return nil +} + // https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom type createRoomResponse struct { RoomID string `json:"room_id"` @@ -42,26 +64,23 @@ func CreateRoom(req *http.Request) util.JSONResponse { return *resErr } // TODO: apply rate-limit - // TODO: parse room_alias_name - // TODO: parse invite list (all valid user ids) - // TODO: invite 3pid list (all valid 3pids) - // TODO: visibility + + if resErr = r.Validate(); resErr != nil { + return *resErr + } + + // TODO: visibility/presets/raw initial state/creation content hostname := "localhost" roomID := fmt.Sprintf("!%s:%s", util.RandomString(16), hostname) + // TODO: Check room ID doesn't clash with an existing one + // TODO: Create room alias association logger.WithFields(log.Fields{ "userID": userID, "roomID": roomID, }).Info("Creating room") - // TODO: Check room ID doesn't clash with an existing one - // TODO: Create room alias association - - // TODO: handle preset - // TODO: handle raw initial state - // TODO: handle creation content - // send events into the room in order of: // 1- m.room.create // 2- room creator join member