From d7dfe034d30033f651c0dfc62637b0806d396022 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 1 Aug 2017 18:04:39 +0100 Subject: [PATCH] Implement membership endpoints --- .../dendrite/clientapi/events/eventcontent.go | 1 + .../dendrite/clientapi/events/events.go | 71 +++++++++ .../dendrite/clientapi/routing/routing.go | 6 + .../dendrite/clientapi/writers/membership.go | 137 ++++++++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 src/github.com/matrix-org/dendrite/clientapi/events/events.go create mode 100644 src/github.com/matrix-org/dendrite/clientapi/writers/membership.go diff --git a/src/github.com/matrix-org/dendrite/clientapi/events/eventcontent.go b/src/github.com/matrix-org/dendrite/clientapi/events/eventcontent.go index 8fed23596..e16b54004 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/events/eventcontent.go +++ b/src/github.com/matrix-org/dendrite/clientapi/events/eventcontent.go @@ -25,6 +25,7 @@ type MemberContent struct { Membership string `json:"membership"` DisplayName string `json:"displayname,omitempty"` AvatarURL string `json:"avatar_url,omitempty"` + Reason string `json:"reason,omitempty"` // TODO: ThirdPartyInvite string `json:"third_party_invite,omitempty"` } diff --git a/src/github.com/matrix-org/dendrite/clientapi/events/events.go b/src/github.com/matrix-org/dendrite/clientapi/events/events.go new file mode 100644 index 000000000..0e47eaaab --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/events/events.go @@ -0,0 +1,71 @@ +// Copyright 2017 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package events + +import ( + "errors" + + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/gomatrixserverlib" +) + +// ErrRoomNoExists is returned when trying to lookup the state of a room that +// doesn't exist +var ErrRoomNoExists = errors.New("Room does not exist") + +// FillBuilder fills the PrevEvents, AuthEvents and Depth fields of an event builder +// using the roomserver query API client provided +// Returns ErrRoomNoExists if the state of the room could not be retrieved because +// the room doesn't exist +// Returns an error if something else went wrong +func FillBuilder( + builder *gomatrixserverlib.EventBuilder, queryAPI api.RoomserverQueryAPI, +) error { + eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder) + if err != nil { + return err + } + + // Ask the roomserver for information about this room + queryReq := api.QueryLatestEventsAndStateRequest{ + RoomID: builder.RoomID, + StateToFetch: eventsNeeded.Tuples(), + } + var queryRes api.QueryLatestEventsAndStateResponse + if queryErr := queryAPI.QueryLatestEventsAndState(&queryReq, &queryRes); queryErr != nil { + return err + } + + if !queryRes.RoomExists { + return ErrRoomNoExists + } + + builder.Depth = queryRes.Depth + builder.PrevEvents = queryRes.LatestEvents + + authEvents := gomatrixserverlib.NewAuthEvents(nil) + + for i := range queryRes.StateEvents { + authEvents.AddEvent(&queryRes.StateEvents[i]) + } + + refs, err := eventsNeeded.AuthEventReferences(&authEvents) + if err != nil { + return err + } + builder.AuthEvents = refs + + return nil +} 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 5a2935e81..ca918824a 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -82,6 +82,12 @@ func Setup( ) }), ) + r0mux.Handle("/rooms/{roomID}/{membership:(?:join|kick|ban|unban|leave|invite)}", + common.MakeAuthAPI("membership", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + vars := mux.Vars(req) + return writers.SendMembership(req, accountDB, device, vars["roomID"], vars["membership"], cfg, queryAPI, producer) + }), + ).Methods("POST", "OPTIONS") r0mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}", common.MakeAuthAPI("send_message", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { vars := mux.Vars(req) diff --git a/src/github.com/matrix-org/dendrite/clientapi/writers/membership.go b/src/github.com/matrix-org/dendrite/clientapi/writers/membership.go new file mode 100644 index 000000000..6521b57b2 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/writers/membership.go @@ -0,0 +1,137 @@ +// Copyright 2017 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package writers + +import ( + "fmt" + "net/http" + "time" + + "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/clientapi/auth/storage/accounts" + "github.com/matrix-org/dendrite/clientapi/events" + "github.com/matrix-org/dendrite/clientapi/httputil" + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/clientapi/producers" + "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/gomatrixserverlib" + + "github.com/matrix-org/util" +) + +// SendMembership implements PUT /rooms/{roomID}/(join|kick|ban|unban|leave|invite) +// by building a m.room.member event then sending it to the room server +func SendMembership( + req *http.Request, accountDB *accounts.Database, device *authtypes.Device, + roomID string, membership string, cfg config.Dendrite, + queryAPI api.RoomserverQueryAPI, producer *producers.RoomserverProducer, +) util.JSONResponse { + localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID) + if err != nil { + return httputil.LogThenError(req, err) + } + + profile, err := accountDB.GetProfileByLocalpart(localpart) + if err != nil { + return httputil.LogThenError(req, err) + } + + stateKey, reason, reqErr := getMembershipStateKey(req, device, membership) + if reqErr != nil { + return *reqErr + } + + builder := gomatrixserverlib.EventBuilder{ + Sender: device.UserID, + RoomID: roomID, + Type: "m.room.member", + StateKey: &stateKey, + } + + // "unban" or "kick" isn't a valid membership value, change it to "leave" + if membership == "unban" || membership == "kick" { + membership = "leave" + } + + content := events.MemberContent{ + Membership: membership, + DisplayName: profile.AvatarURL, + AvatarURL: profile.AvatarURL, + Reason: reason, + } + + if err = builder.SetContent(content); err != nil { + return httputil.LogThenError(req, err) + } + + if err = events.FillBuilder(&builder, queryAPI); err == events.ErrRoomNoExists { + return util.JSONResponse{ + Code: 404, + JSON: jsonerror.NotFound(err.Error()), + } + } else if err != nil { + return httputil.LogThenError(req, err) + } + + eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), cfg.Matrix.ServerName) + now := time.Now() + event, err := builder.Build(eventID, now, cfg.Matrix.ServerName, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey) + if err != nil { + return httputil.LogThenError(req, err) + } + + if err := producer.SendEvents([]gomatrixserverlib.Event{event}, cfg.Matrix.ServerName); err != nil { + return httputil.LogThenError(req, err) + } + + return util.JSONResponse{ + Code: 200, + JSON: struct{}{}, + } +} + +func getMembershipStateKey( + req *http.Request, device *authtypes.Device, membership string, +) (stateKey string, reason string, response *util.JSONResponse) { + if membership == "ban" || membership == "unban" || membership == "kick" { + // If we're in this case, the state key is contained in the request body, + // possibly along with a reason (for "kick" and "ban") so we need to parse + // it + var requestBody struct { + UserID string `json:"user_id"` + Reason string `json:"reason"` + } + + if reqErr := httputil.UnmarshalJSONRequest(req, &requestBody); reqErr != nil { + response = reqErr + return + } + if requestBody.UserID == "" { + response = &util.JSONResponse{ + Code: 400, + JSON: jsonerror.BadJSON("'user_id' must be supplied."), + } + return + } + + stateKey = requestBody.UserID + reason = requestBody.Reason + } else { + stateKey = device.UserID + } + + return +}