From 61adfa125c0466c7fcf69133985c500d9f26f4c1 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 26 Jul 2017 18:57:42 +0100 Subject: [PATCH] Save room alias --- .../dendrite/clientapi/readers/directory.go | 49 ++++++ .../dendrite/clientapi/routing/routing.go | 9 +- .../dendrite/cmd/dendrite-room-server/main.go | 14 +- .../dendrite/roomserver/api/query.go | 31 ++++ .../dendrite/roomserver/query/query.go | 143 +++++++++++++++++- .../roomserver/storage/room_aliases_table.go | 2 +- 6 files changed, 238 insertions(+), 10 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/clientapi/readers/directory.go b/src/github.com/matrix-org/dendrite/clientapi/readers/directory.go index 336f29fed..68f59a1a5 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/readers/directory.go +++ b/src/github.com/matrix-org/dendrite/clientapi/readers/directory.go @@ -22,11 +22,16 @@ import ( "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" ) +type roomID struct { + RoomID string `json:"room_id"` +} + // DirectoryRoom looks up a room alias func DirectoryRoom( req *http.Request, @@ -69,3 +74,47 @@ func DirectoryRoom( } } } + +// SetLocalAlias implements PUT /directory/room/{roomAlias} +func SetLocalAlias( + req *http.Request, + device *authtypes.Device, + alias string, + cfg *config.Dendrite, + queryAPI api.RoomserverQueryAPI, +) util.JSONResponse { + _, domain, err := gomatrixserverlib.SplitID('#', alias) + if err != nil { + return util.JSONResponse{ + Code: 400, + JSON: jsonerror.BadJSON("Room alias must be in the form '#localpart:domain'"), + } + } + + if domain != cfg.Matrix.ServerName { + return util.JSONResponse{ + Code: 403, + JSON: jsonerror.Forbidden("Alias must be on local homeserver"), + } + } + + var r roomID + if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil { + return *resErr + } + + queryReq := api.SetRoomAliasRequest{ + UserID: device.UserID, + RoomID: r.RoomID, + Alias: alias, + } + var queryRes api.SetRoomAliasResponse + if err := queryAPI.SetRoomAlias(&queryReq, &queryRes); err != nil { + return httputil.LogThenError(req, err) + } + + return util.JSONResponse{ + Code: 200, + JSON: struct{}{}, + } +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go index 93ce9e1e3..839783c48 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -111,7 +111,14 @@ func Setup( vars := mux.Vars(req) return readers.DirectoryRoom(req, device, vars["roomAlias"], federation, &cfg) }), - ) + ).Methods("GET") + + r0mux.Handle("/directory/room/{roomAlias}", + common.MakeAuthAPI("directory_room", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + vars := mux.Vars(req) + return readers.SetLocalAlias(req, device, vars["roomAlias"], &cfg, queryAPI) + }), + ).Methods("PUT", "OPTIONS") r0mux.Handle("/logout", common.MakeAuthAPI("logout", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-room-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-room-server/main.go index 715a40740..7bb016004 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-room-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-room-server/main.go @@ -58,12 +58,6 @@ func main() { panic(err) } - queryAPI := query.RoomserverQueryAPI{ - DB: db, - } - - queryAPI.SetupHTTP(http.DefaultServeMux) - inputAPI := input.RoomserverInputAPI{ DB: db, Producer: kafkaProducer, @@ -72,6 +66,14 @@ func main() { inputAPI.SetupHTTP(http.DefaultServeMux) + queryAPI := query.RoomserverQueryAPI{ + DB: db, + Cfg: cfg, + InputAPI: inputAPI, + } + + queryAPI.SetupHTTP(http.DefaultServeMux) + http.DefaultServeMux.Handle("/metrics", prometheus.Handler()) log.Info("Started room server on ", cfg.Listen.RoomServer) diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/query.go b/src/github.com/matrix-org/dendrite/roomserver/api/query.go index 6e6a838a9..8f77b07bf 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/api/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/api/query.go @@ -100,6 +100,19 @@ type QueryEventsByIDResponse struct { Events []gomatrixserverlib.Event `json:"events"` } +// SetRoomAliasRequest is a request to SetRoomAlias +type SetRoomAliasRequest struct { + // ID of the user setting the alias + UserID string `json:"user_id"` + // New alias for the room + Alias string `json:"alias"` + // The room ID the alias is referring to + RoomID string `json:"room_id"` +} + +// SetRoomAliasResponse is a response to SetRoomAlias +type SetRoomAliasResponse struct{} + // RoomserverQueryAPI is used to query information from the room server. type RoomserverQueryAPI interface { // Query the latest events and state for a room from the room server. @@ -119,6 +132,12 @@ type RoomserverQueryAPI interface { request *QueryEventsByIDRequest, response *QueryEventsByIDResponse, ) error + + // Set a room alias + SetRoomAlias( + request *SetRoomAliasRequest, + response *SetRoomAliasResponse, + ) error } // RoomserverQueryLatestEventsAndStatePath is the HTTP path for the QueryLatestEventsAndState API. @@ -130,6 +149,9 @@ const RoomserverQueryStateAfterEventsPath = "/api/roomserver/queryStateAfterEven // RoomserverQueryEventsByIDPath is the HTTP path for the QueryEventsByID API. const RoomserverQueryEventsByIDPath = "/api/roomserver/queryEventsByID" +// RoomserverSetRoomAliasPath is the HTTP path for the SetRoomAlias API. +const RoomserverSetRoomAliasPath = "/api/roomserver/setRoomAlias" + // NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API. // If httpClient is nil then it uses the http.DefaultClient func NewRoomserverQueryAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverQueryAPI { @@ -171,6 +193,15 @@ func (h *httpRoomserverQueryAPI) QueryEventsByID( return postJSON(h.httpClient, apiURL, request, response) } +// SetRoomAlias implements RoomserverQueryAPI +func (h *httpRoomserverQueryAPI) SetRoomAlias( + request *SetRoomAliasRequest, + response *SetRoomAliasResponse, +) error { + apiURL := h.roomserverURL + RoomserverSetRoomAliasPath + return postJSON(h.httpClient, apiURL, request, response) +} + func postJSON(httpClient *http.Client, apiURL string, request, response interface{}) error { jsonBytes, err := json.Marshal(request) if err != nil { diff --git a/src/github.com/matrix-org/dendrite/roomserver/query/query.go b/src/github.com/matrix-org/dendrite/roomserver/query/query.go index 142df90e3..deb339830 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/query/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/query/query.go @@ -16,10 +16,14 @@ package query import ( "encoding/json" + "fmt" "net/http" + "time" "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/common/config" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/roomserver/input" "github.com/matrix-org/dendrite/roomserver/state" "github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/gomatrixserverlib" @@ -40,11 +44,25 @@ type RoomserverQueryAPIDatabase interface { // Lookup the numeric IDs for a list of events. // Returns an error if there was a problem talking to the database. EventNIDs(eventIDs []string) (map[string]types.EventNID, error) + // Save a given room alias with the room ID it refers to. + // Returns an error if there was a problem talking to the database. + SetRoomAlias(alias string, roomID string) error + // Lookup the room ID a given alias refers to. + // Returns an error if there was a problem talking to the database. + GetRoomIDFromAlias(alias string) (string, error) + // Lookup all aliases referring to a given room ID. + // Returns an error if there was a problem talking to the database. + GetAliasesFromRoomID(roomID string) ([]string, error) + // Remove a given room alias. + // Returns an error if there was a problem talking to the database. + RemoveRoomAlias(alias string) error } -// RoomserverQueryAPI is an implementation of RoomserverQueryAPI +// RoomserverQueryAPI is an implementation of api.RoomserverQueryAPI type RoomserverQueryAPI struct { - DB RoomserverQueryAPIDatabase + DB RoomserverQueryAPIDatabase + Cfg *config.Dendrite + InputAPI input.RoomserverInputAPI } // QueryLatestEventsAndState implements api.RoomserverQueryAPI @@ -149,6 +167,113 @@ func (r *RoomserverQueryAPI) QueryEventsByID( return nil } +// SetRoomAlias implements api.RoomserverQueryAPI +func (r *RoomserverQueryAPI) SetRoomAlias( + request *api.SetRoomAliasRequest, + response *api.SetRoomAliasResponse, +) error { + // Save the new alias + // TODO: Check if alias already exists + if err := r.DB.SetRoomAlias(request.Alias, request.RoomID); err != nil { + return err + } + + // Send a m.room.aliases event with the updated list of aliases for this room + if err := r.sendUpdatedAliasesEvent(request.UserID, request.RoomID); err != nil { + return err + } + + // We don't need to return anything in the response, so we don't edit it + return nil +} + +type roomAliasesContent struct { + Aliases []string `json:"aliases"` +} + +// Build the updated m.room.aliases event to send to the room after addition or +// removal of an alias +func (r *RoomserverQueryAPI) sendUpdatedAliasesEvent(userID string, roomID string) error { + serverName := string(r.Cfg.Matrix.ServerName) + + builder := gomatrixserverlib.EventBuilder{ + Sender: userID, + RoomID: roomID, + Type: "m.room.aliases", + StateKey: &serverName, + } + + // Retrieve the updated list of aliases, marhal it and set it as the + // event's content + aliases, err := r.DB.GetAliasesFromRoomID(roomID) + if err != nil { + return err + } + content := roomAliasesContent{Aliases: aliases} + rawContent, err := json.Marshal(content) + if err != nil { + return err + } + err = builder.SetContent(json.RawMessage(rawContent)) + if err != nil { + return err + } + + // Get needed state events and depth + eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(&builder) + if err != nil { + return err + } + req := api.QueryLatestEventsAndStateRequest{ + RoomID: roomID, + StateToFetch: eventsNeeded.Tuples(), + } + var res api.QueryLatestEventsAndStateResponse + if err = r.QueryLatestEventsAndState(&req, &res); err != nil { + return err + } + builder.Depth = res.Depth + builder.PrevEvents = res.LatestEvents + + // Add auth events + authEvents := gomatrixserverlib.NewAuthEvents(nil) + for i := range res.StateEvents { + authEvents.AddEvent(&res.StateEvents[i]) + } + refs, err := eventsNeeded.AuthEventReferences(&authEvents) + if err != nil { + return err + } + builder.AuthEvents = refs + + // Build the event + eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), r.Cfg.Matrix.ServerName) + now := time.Now() + event, err := builder.Build(eventID, now, r.Cfg.Matrix.ServerName, r.Cfg.Matrix.KeyID, r.Cfg.Matrix.PrivateKey) + if err != nil { + return err + } + + // Create the request + ire := api.InputRoomEvent{ + Kind: api.KindNew, + Event: event, + AuthEventIDs: event.AuthEventIDs(), + SendAsServer: serverName, + } + inputReq := api.InputRoomEventsRequest{ + InputRoomEvents: []api.InputRoomEvent{ire}, + } + var inputRes api.InputRoomEventsResponse + + // Send the request + if err := r.InputAPI.InputRoomEvents(&inputReq, &inputRes); err != nil { + return err + } + + return nil +} + func (r *RoomserverQueryAPI) loadStateEvents(stateEntries []types.StateEntry) ([]gomatrixserverlib.Event, error) { eventNIDs := make([]types.EventNID, len(stateEntries)) for i := range stateEntries { @@ -214,4 +339,18 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) { return util.JSONResponse{Code: 200, JSON: &response} }), ) + servMux.Handle( + api.RoomserverSetRoomAliasPath, + common.MakeAPI("setRoomAlias", func(req *http.Request) util.JSONResponse { + var request api.SetRoomAliasRequest + var response api.SetRoomAliasResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.SetRoomAlias(&request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: 200, JSON: &response} + }), + ) } diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go index ed90fbd57..09e1b181c 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go @@ -72,7 +72,7 @@ func (s *roomAliasesStatements) selectRoomIDFromAlias(alias string) (roomID stri func (s *roomAliasesStatements) selectAliasesFromRoomID(roomID string) (aliases []string, err error) { aliases = []string{} - rows, err := s.selectRoomIDFromAliasStmt.Query(roomID) + rows, err := s.selectAliasesFromRoomIDStmt.Query(roomID) if err != nil { return }