aboutsummaryrefslogtreecommitdiff
path: root/roomserver
diff options
context:
space:
mode:
authorNeil Alexander <neilalexander@users.noreply.github.com>2020-05-04 13:53:47 +0100
committerGitHub <noreply@github.com>2020-05-04 13:53:47 +0100
commit5c894efd0ee67bf911b9c3b5428a61ddc58819de (patch)
tree3b576827ad15d26eaa09423492231c00893dec54 /roomserver
parent36bbb255614d289a3417194d4d2ac129e339f531 (diff)
Roomserver perform join (#1001)
* Add PerformJoin template * Try roomserver perform join * Send correct server name to FS API * Pass through content, try to handle multiple server names * Fix local server checks * Don't refer to non-existent error * Add directory lookups of aliases * Remove unneeded parameters * Don't repeat join events into the roomserver * Unmarshal the content, that would help * Check if the user is already in the room in the fedeationapi too * Return incompatible room version error * Use Membership, don't try more servers than needed * Review comments, make FS API take list of servernames, dedupe them, break out of loop properly on success * Tweaks
Diffstat (limited to 'roomserver')
-rw-r--r--roomserver/api/api.go6
-rw-r--r--roomserver/api/perform.go59
-rw-r--r--roomserver/internal/api.go13
-rw-r--r--roomserver/internal/perform_join.go199
4 files changed, 277 insertions, 0 deletions
diff --git a/roomserver/api/api.go b/roomserver/api/api.go
index c12dbddd..ae4beab2 100644
--- a/roomserver/api/api.go
+++ b/roomserver/api/api.go
@@ -18,6 +18,12 @@ type RoomserverInternalAPI interface {
response *InputRoomEventsResponse,
) error
+ PerformJoin(
+ ctx context.Context,
+ req *PerformJoinRequest,
+ res *PerformJoinResponse,
+ ) error
+
// Query the latest events and state for a room from the room server.
QueryLatestEventsAndState(
ctx context.Context,
diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go
new file mode 100644
index 00000000..e60c078b
--- /dev/null
+++ b/roomserver/api/perform.go
@@ -0,0 +1,59 @@
+package api
+
+import (
+ "context"
+
+ commonHTTP "github.com/matrix-org/dendrite/common/http"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/opentracing/opentracing-go"
+)
+
+const (
+ // RoomserverPerformJoinPath is the HTTP path for the PerformJoin API.
+ RoomserverPerformJoinPath = "/api/roomserver/performJoin"
+
+ // RoomserverPerformLeavePath is the HTTP path for the PerformLeave API.
+ RoomserverPerformLeavePath = "/api/roomserver/performLeave"
+)
+
+type PerformJoinRequest struct {
+ RoomIDOrAlias string `json:"room_id_or_alias"`
+ UserID string `json:"user_id"`
+ Content map[string]interface{} `json:"content"`
+ ServerNames []gomatrixserverlib.ServerName `json:"server_names"`
+}
+
+type PerformJoinResponse struct {
+}
+
+func (h *httpRoomserverInternalAPI) PerformJoin(
+ ctx context.Context,
+ request *PerformJoinRequest,
+ response *PerformJoinResponse,
+) error {
+ span, ctx := opentracing.StartSpanFromContext(ctx, "PerformJoin")
+ defer span.Finish()
+
+ apiURL := h.roomserverURL + RoomserverPerformJoinPath
+ return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
+}
+
+type PerformLeaveRequest struct {
+ RoomID string `json:"room_id"`
+ UserID string `json:"user_id"`
+}
+
+type PerformLeaveResponse struct {
+}
+
+func (h *httpRoomserverInternalAPI) PerformLeave(
+ ctx context.Context,
+ request *PerformLeaveRequest,
+ response *PerformLeaveResponse,
+) error {
+ span, ctx := opentracing.StartSpanFromContext(ctx, "PerformLeave")
+ defer span.Finish()
+
+ apiURL := h.roomserverURL + RoomserverPerformLeavePath
+ return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
+}
diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go
index d1c443f2..1dc985ef 100644
--- a/roomserver/internal/api.go
+++ b/roomserver/internal/api.go
@@ -46,6 +46,19 @@ func (r *RoomserverInternalAPI) SetupHTTP(servMux *http.ServeMux) {
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}),
)
+ servMux.Handle(api.RoomserverPerformJoinPath,
+ common.MakeInternalAPI("performJoin", func(req *http.Request) util.JSONResponse {
+ var request api.PerformJoinRequest
+ var response api.PerformJoinResponse
+ if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
+ return util.MessageResponse(http.StatusBadRequest, err.Error())
+ }
+ if err := r.PerformJoin(req.Context(), &request, &response); err != nil {
+ return util.ErrorResponse(err)
+ }
+ return util.JSONResponse{Code: http.StatusOK, JSON: &response}
+ }),
+ )
servMux.Handle(
api.RoomserverQueryLatestEventsAndStatePath,
common.MakeInternalAPI("queryLatestEventsAndState", func(req *http.Request) util.JSONResponse {
diff --git a/roomserver/internal/perform_join.go b/roomserver/internal/perform_join.go
new file mode 100644
index 00000000..3dfa118f
--- /dev/null
+++ b/roomserver/internal/perform_join.go
@@ -0,0 +1,199 @@
+package internal
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/matrix-org/dendrite/common"
+ fsAPI "github.com/matrix-org/dendrite/federationsender/api"
+ "github.com/matrix-org/dendrite/roomserver/api"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/sirupsen/logrus"
+)
+
+// WriteOutputEvents implements OutputRoomEventWriter
+func (r *RoomserverInternalAPI) PerformJoin(
+ ctx context.Context,
+ req *api.PerformJoinRequest,
+ res *api.PerformJoinResponse,
+) error {
+ _, domain, err := gomatrixserverlib.SplitID('@', req.UserID)
+ if err != nil {
+ return fmt.Errorf("Supplied user ID %q in incorrect format", req.UserID)
+ }
+ if domain != r.Cfg.Matrix.ServerName {
+ return fmt.Errorf("User %q does not belong to this homeserver", req.UserID)
+ }
+ if strings.HasPrefix(req.RoomIDOrAlias, "!") {
+ return r.performJoinRoomByID(ctx, req, res)
+ }
+ if strings.HasPrefix(req.RoomIDOrAlias, "#") {
+ return r.performJoinRoomByAlias(ctx, req, res)
+ }
+ return fmt.Errorf("Room ID or alias %q is invalid", req.RoomIDOrAlias)
+}
+
+func (r *RoomserverInternalAPI) performJoinRoomByAlias(
+ ctx context.Context,
+ req *api.PerformJoinRequest,
+ res *api.PerformJoinResponse,
+) error {
+ // Get the domain part of the room alias.
+ _, domain, err := gomatrixserverlib.SplitID('#', req.RoomIDOrAlias)
+ if err != nil {
+ return fmt.Errorf("Alias %q is not in the correct format", req.RoomIDOrAlias)
+ }
+ req.ServerNames = append(req.ServerNames, domain)
+
+ // Check if this alias matches our own server configuration. If it
+ // doesn't then we'll need to try a federated join.
+ var roomID string
+ if domain != r.Cfg.Matrix.ServerName {
+ // The alias isn't owned by us, so we will need to try joining using
+ // a remote server.
+ dirReq := fsAPI.PerformDirectoryLookupRequest{
+ RoomAlias: req.RoomIDOrAlias, // the room alias to lookup
+ ServerName: domain, // the server to ask
+ }
+ dirRes := fsAPI.PerformDirectoryLookupResponse{}
+ err = r.fsAPI.PerformDirectoryLookup(ctx, &dirReq, &dirRes)
+ if err != nil {
+ logrus.WithError(err).Errorf("error looking up alias %q", req.RoomIDOrAlias)
+ return fmt.Errorf("Looking up alias %q over federation failed: %w", req.RoomIDOrAlias, err)
+ }
+ roomID = dirRes.RoomID
+ req.ServerNames = append(req.ServerNames, dirRes.ServerNames...)
+ } else {
+ // Otherwise, look up if we know this room alias locally.
+ roomID, err = r.DB.GetRoomIDForAlias(ctx, req.RoomIDOrAlias)
+ if err != nil {
+ return fmt.Errorf("Lookup room alias %q failed: %w", req.RoomIDOrAlias, err)
+ }
+ }
+
+ // If the room ID is empty then we failed to look up the alias.
+ if roomID == "" {
+ return fmt.Errorf("Alias %q not found", req.RoomIDOrAlias)
+ }
+
+ // If we do, then pluck out the room ID and continue the join.
+ req.RoomIDOrAlias = roomID
+ return r.performJoinRoomByID(ctx, req, res)
+}
+
+// TODO: Break this function up a bit
+// nolint:gocyclo
+func (r *RoomserverInternalAPI) performJoinRoomByID(
+ ctx context.Context,
+ req *api.PerformJoinRequest,
+ res *api.PerformJoinResponse, // nolint:unparam
+) error {
+ // Get the domain part of the room ID.
+ _, domain, err := gomatrixserverlib.SplitID('!', req.RoomIDOrAlias)
+ if err != nil {
+ return fmt.Errorf("Room ID %q is invalid", req.RoomIDOrAlias)
+ }
+ req.ServerNames = append(req.ServerNames, domain)
+
+ // Prepare the template for the join event.
+ userID := req.UserID
+ eb := gomatrixserverlib.EventBuilder{
+ Type: gomatrixserverlib.MRoomMember,
+ Sender: userID,
+ StateKey: &userID,
+ RoomID: req.RoomIDOrAlias,
+ Redacts: "",
+ }
+ if err = eb.SetUnsigned(struct{}{}); err != nil {
+ return fmt.Errorf("eb.SetUnsigned: %w", err)
+ }
+
+ // It is possible for the request to include some "content" for the
+ // event. We'll always overwrite the "membership" key, but the rest,
+ // like "display_name" or "avatar_url", will be kept if supplied.
+ if req.Content == nil {
+ req.Content = map[string]interface{}{}
+ }
+ req.Content["membership"] = "join"
+ if err = eb.SetContent(req.Content); err != nil {
+ return fmt.Errorf("eb.SetContent: %w", err)
+ }
+
+ // Try to construct an actual join event from the template.
+ // If this succeeds then it is a sign that the room already exists
+ // locally on the homeserver.
+ // TODO: Check what happens if the room exists on the server
+ // but everyone has since left. I suspect it does the wrong thing.
+ buildRes := api.QueryLatestEventsAndStateResponse{}
+ event, err := common.BuildEvent(
+ ctx, // the request context
+ &eb, // the template join event
+ r.Cfg, // the server configuration
+ time.Now(), // the event timestamp to use
+ r, // the roomserver API to use
+ &buildRes, // the query response
+ )
+
+ switch err {
+ case nil:
+ // The room join is local. Send the new join event into the
+ // roomserver. First of all check that the user isn't already
+ // a member of the room.
+ alreadyJoined := false
+ for _, se := range buildRes.StateEvents {
+ if membership, merr := se.Membership(); merr == nil {
+ if se.StateKey() != nil && *se.StateKey() == *event.StateKey() {
+ alreadyJoined = (membership == "join")
+ break
+ }
+ }
+ }
+
+ // If we haven't already joined the room then send an event
+ // into the room changing our membership status.
+ if !alreadyJoined {
+ inputReq := api.InputRoomEventsRequest{
+ InputRoomEvents: []api.InputRoomEvent{
+ api.InputRoomEvent{
+ Kind: api.KindNew,
+ Event: event.Headered(buildRes.RoomVersion),
+ AuthEventIDs: event.AuthEventIDs(),
+ SendAsServer: string(r.Cfg.Matrix.ServerName),
+ },
+ },
+ }
+ inputRes := api.InputRoomEventsResponse{}
+ if err = r.InputRoomEvents(ctx, &inputReq, &inputRes); err != nil {
+ return fmt.Errorf("r.InputRoomEvents: %w", err)
+ }
+ }
+
+ case common.ErrRoomNoExists:
+ // The room doesn't exist. First of all check if the room is a local
+ // 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 {
+ return fmt.Errorf("Room ID %q does not exist", req.RoomIDOrAlias)
+ }
+
+ // Try joining by all of the supplied server names.
+ fedReq := fsAPI.PerformJoinRequest{
+ RoomID: req.RoomIDOrAlias, // the room ID to try and join
+ UserID: req.UserID, // the user ID joining the room
+ ServerNames: req.ServerNames, // the server to try joining with
+ Content: req.Content, // the membership event content
+ }
+ fedRes := fsAPI.PerformJoinResponse{}
+ err = r.fsAPI.PerformJoin(ctx, &fedReq, &fedRes)
+ if err != nil {
+ return fmt.Errorf("Error joining federated room: %q", err)
+ }
+
+ default:
+ return fmt.Errorf("Error joining room %q: %w", req.RoomIDOrAlias, err)
+ }
+
+ return nil
+}