From db95c40484c7cbfba0b6b77060aff3792dafbc8e Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 8 Jun 2018 13:58:47 +0100 Subject: [PATCH] Query whether a room alias exists on app services Signed-off-by: Andrew Morgan --- .../dendrite/appservice/api/query.go | 40 +++++- .../dendrite/appservice/appservice.go | 14 +-- .../appservice/consumers/roomserver.go | 1 + .../dendrite/appservice/query/query.go | 80 ++++++++++++ .../dendrite/clientapi/clientapi.go | 4 +- .../dendrite/clientapi/routing/directory.go | 115 ++++++++++++------ .../dendrite/clientapi/routing/routing.go | 11 +- .../cmd/dendrite-client-api-server/main.go | 3 +- .../cmd/dendrite-monolith-server/main.go | 7 +- .../dendrite/roomserver/alias/alias.go | 10 +- 10 files changed, 226 insertions(+), 59 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/appservice/api/query.go b/src/github.com/matrix-org/dendrite/appservice/api/query.go index b094c9149..62f61c0ab 100644 --- a/src/github.com/matrix-org/dendrite/appservice/api/query.go +++ b/src/github.com/matrix-org/dendrite/appservice/api/query.go @@ -18,16 +18,41 @@ package api import ( + "context" "net/http" + + commonHTTP "github.com/matrix-org/dendrite/common/http" + opentracing "github.com/opentracing/opentracing-go" ) +// RoomAliasExistsRequest is a request to an application service +// about whether a room alias exists +type RoomAliasExistsRequest struct { + // Alias we want to lookup + Alias string `json:"alias"` +} + +// RoomAliasExistsResponse is a response from an application service +// about whether a room alias exists +type RoomAliasExistsResponse struct { + AliasExists bool `json:"exists"` +} + // AppServiceQueryAPI is used to query user and room alias data from application // services type AppServiceQueryAPI interface { - // TODO: Check whether a room alias exists within any application service namespaces + // Check whether a room alias exists within any application service namespaces + RoomAliasExists( + ctx context.Context, + req *RoomAliasExistsRequest, + response *RoomAliasExistsResponse, + ) error // TODO: QueryUserIDExists } +// AppServiceRoomAliasExistsPath is the HTTP path for the RoomAliasExists API +const AppServiceRoomAliasExistsPath = "/api/appservice/RoomAliasExists" + // httpAppServiceQueryAPI contains the URL to an appservice query API and a // reference to a httpClient used to reach it type httpAppServiceQueryAPI struct { @@ -47,3 +72,16 @@ func NewAppServiceQueryAPIHTTP( } return &httpAppServiceQueryAPI{appserviceURL, httpClient} } + +// RoomAliasExists implements AppServiceQueryAPI +func (h *httpAppServiceQueryAPI) RoomAliasExists( + ctx context.Context, + request *RoomAliasExistsRequest, + response *RoomAliasExistsResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "appserviceRoomAliasExists") + defer span.Finish() + + apiURL := h.appserviceURL + AppServiceRoomAliasExistsPath + return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} diff --git a/src/github.com/matrix-org/dendrite/appservice/appservice.go b/src/github.com/matrix-org/dendrite/appservice/appservice.go index 5d2fc085a..e33793d2f 100644 --- a/src/github.com/matrix-org/dendrite/appservice/appservice.go +++ b/src/github.com/matrix-org/dendrite/appservice/appservice.go @@ -74,15 +74,13 @@ func SetupAppServiceAPIComponent( } } - // Create a HTTP client that this component will use for all outbound and - // inbound requests (inbound only for the internal API) - httpClient := &http.Client{ - Timeout: time.Second * 30, - } - + // Create appserivce query API with an HTTP client that will be used for all + // outbound and inbound requests (inbound only for the internal API) appserviceQueryAPI := query.AppServiceQueryAPI{ - HTTPClient: httpClient, - Cfg: base.Cfg, + HTTPClient: &http.Client{ + Timeout: time.Second * 30, + }, + Cfg: base.Cfg, } appserviceQueryAPI.SetupHTTP(http.DefaultServeMux) diff --git a/src/github.com/matrix-org/dendrite/appservice/consumers/roomserver.go b/src/github.com/matrix-org/dendrite/appservice/consumers/roomserver.go index b3584dfb8..dbdae5320 100644 --- a/src/github.com/matrix-org/dendrite/appservice/consumers/roomserver.go +++ b/src/github.com/matrix-org/dendrite/appservice/consumers/roomserver.go @@ -185,6 +185,7 @@ func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Cont return false } + // Check Room ID and Sender of the event if appservice.IsInterestedInUserID(event.Sender()) || appservice.IsInterestedInRoomID(event.RoomID()) { return true diff --git a/src/github.com/matrix-org/dendrite/appservice/query/query.go b/src/github.com/matrix-org/dendrite/appservice/query/query.go index cdaf681ab..3298661f0 100644 --- a/src/github.com/matrix-org/dendrite/appservice/query/query.go +++ b/src/github.com/matrix-org/dendrite/appservice/query/query.go @@ -17,18 +17,98 @@ package query import ( + "context" + "encoding/json" "net/http" + "github.com/matrix-org/dendrite/appservice/api" + "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/util" + opentracing "github.com/opentracing/opentracing-go" + log "github.com/sirupsen/logrus" ) +const remoteAppServiceRoomAliasExistsPath = "/rooms/" + // AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI type AppServiceQueryAPI struct { HTTPClient *http.Client Cfg *config.Dendrite } +// RoomAliasExists performs a request to '/room/{roomAlias}' on all known +// handling application services until one admits to owning the room +func (a *AppServiceQueryAPI) RoomAliasExists( + ctx context.Context, + request *api.RoomAliasExistsRequest, + response *api.RoomAliasExistsResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "ApplicationServiceRoomAlias") + defer span.Finish() + + // Determine which application service should handle this request + for _, appservice := range a.Cfg.Derived.ApplicationServices { + if appservice.URL != "" && appservice.IsInterestedInRoomAlias(request.Alias) { + // The full path to the rooms API, includes hs token + apiURL := appservice.URL + + remoteAppServiceRoomAliasExistsPath + request.Alias + "?access_token=" + appservice.HSToken + + // Send a request to each application service. If one responds that it has + // created the room, immediately return. + req, err := http.NewRequest(http.MethodGet, apiURL, nil) + if err != nil { + return err + } + resp, err := a.HTTPClient.Do(req.WithContext(ctx)) + if resp != nil { + defer func() { + err = resp.Body.Close() + if err != nil { + log.WithFields(log.Fields{ + "appservice_id": appservice.ID, + "status_code": resp.StatusCode, + }).Error("Unable to close application service response body") + } + }() + } + if err != nil { + log.WithError(err).Errorf("Issue querying room alias on application service %s", appservice.ID) + return err + } + if resp.StatusCode == http.StatusOK { + // StatusOK received from appservice. Room exists + response.AliasExists = true + return nil + } + + // Log non OK + log.WithFields(log.Fields{ + "appservice_id": appservice.ID, + "status_code": resp.StatusCode, + }).Warn("Application service responded with non-OK status code") + } + } + + response.AliasExists = false + return nil +} + // SetupHTTP adds the AppServiceQueryPAI handlers to the http.ServeMux. This // handles and muxes incoming api requests the to internal AppServiceQueryAPI. func (a *AppServiceQueryAPI) SetupHTTP(servMux *http.ServeMux) { + servMux.Handle( + api.AppServiceRoomAliasExistsPath, + common.MakeInternalAPI("appserviceRoomAliasExists", func(req *http.Request) util.JSONResponse { + var request api.RoomAliasExistsRequest + var response api.RoomAliasExistsResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := a.RoomAliasExists(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) } diff --git a/src/github.com/matrix-org/dendrite/clientapi/clientapi.go b/src/github.com/matrix-org/dendrite/clientapi/clientapi.go index 362e251c1..cba84fbb2 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/clientapi.go +++ b/src/github.com/matrix-org/dendrite/clientapi/clientapi.go @@ -15,6 +15,7 @@ package clientapi import ( + appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/auth/storage/accounts" "github.com/matrix-org/dendrite/clientapi/auth/storage/devices" "github.com/matrix-org/dendrite/clientapi/consumers" @@ -40,6 +41,7 @@ func SetupClientAPIComponent( inputAPI roomserverAPI.RoomserverInputAPI, queryAPI roomserverAPI.RoomserverQueryAPI, typingInputAPI typingServerAPI.TypingServerInputAPI, + asAPI appserviceAPI.AppServiceQueryAPI, transactionsCache *transactions.Cache, ) { roomserverProducer := producers.NewRoomserverProducer(inputAPI) @@ -63,7 +65,7 @@ func SetupClientAPIComponent( } routing.Setup( - base.APIMux, *base.Cfg, roomserverProducer, queryAPI, aliasAPI, + base.APIMux, *base.Cfg, roomserverProducer, queryAPI, asAPI, aliasAPI, accountsDB, deviceDB, federation, *keyRing, userUpdateProducer, syncProducer, typingProducer, transactionsCache, ) diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/directory.go b/src/github.com/matrix-org/dendrite/clientapi/routing/directory.go index 6f5765f4b..8721be8e3 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/directory.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/directory.go @@ -15,25 +15,29 @@ package routing import ( + "fmt" "net/http" + appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "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" + roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" ) // DirectoryRoom looks up a room alias +// nolint: gocyclo func DirectoryRoom( req *http.Request, roomAlias string, federation *gomatrixserverlib.FederationClient, cfg *config.Dendrite, - aliasAPI api.RoomserverAliasAPI, + rsAPI roomserverAPI.RoomserverAliasAPI, + asAPI appserviceAPI.AppServiceQueryAPI, ) util.JSONResponse { _, domain, err := gomatrixserverlib.SplitID('#', roomAlias) if err != nil { @@ -43,52 +47,91 @@ func DirectoryRoom( } } - var resp gomatrixserverlib.RespDirectory - if domain == cfg.Matrix.ServerName { - queryReq := api.GetRoomIDForAliasRequest{Alias: roomAlias} - var queryRes api.GetRoomIDForAliasResponse - if err = aliasAPI.GetRoomIDForAlias(req.Context(), &queryReq, &queryRes); err != nil { + queryResp, err := getRoomIDForAlias(req, rsAPI, roomAlias) + if err != nil { return httputil.LogThenError(req, err) } - if len(queryRes.RoomID) > 0 { - // TODO: List servers that are aware of this room alias - resp = gomatrixserverlib.RespDirectory{ - RoomID: queryRes.RoomID, - Servers: []gomatrixserverlib.ServerName{}, - } - } else { - // If the response doesn't contain a non-empty string, return an error + // List any roomIDs found associated with this alias + if len(queryResp.RoomID) > 0 { return util.JSONResponse{ - Code: http.StatusNotFound, - JSON: jsonerror.NotFound("Room alias " + roomAlias + " not found."), + Code: http.StatusOK, + JSON: queryResp, + } + } + + // No rooms found locally, try our application services by making a call to + // the appservice component + aliasReq := appserviceAPI.RoomAliasExistsRequest{Alias: roomAlias} + var aliasResp appserviceAPI.RoomAliasExistsResponse + err = asAPI.RoomAliasExists(req.Context(), &aliasReq, &aliasResp) + if err != nil { + return httputil.LogThenError(req, err) + } + + if aliasResp.AliasExists { + // Query the roomserver API again. We should have the room now + queryResp, err = getRoomIDForAlias(req, rsAPI, roomAlias) + if err != nil { + return httputil.LogThenError(req, err) + } + + // List any roomIDs found associated with this alias + if len(queryResp.RoomID) > 0 { + return util.JSONResponse{ + Code: http.StatusOK, + JSON: queryResp, + } } } } else { - resp, err = federation.LookupRoomAlias(req.Context(), domain, roomAlias) + // Query the federation for this room alias + resp, err := federation.LookupRoomAlias(req.Context(), domain, roomAlias) if err != nil { - switch x := err.(type) { + switch err.(type) { case gomatrix.HTTPError: - if x.Code == http.StatusNotFound { - return util.JSONResponse{ - Code: http.StatusNotFound, - JSON: jsonerror.NotFound("Room alias not found"), - } - } + default: + // TODO: Return 502 if the remote server errored. + // TODO: Return 504 if the remote server timed out. + return httputil.LogThenError(req, err) + } + } + if len(resp.RoomID) > 0 { + return util.JSONResponse{ + Code: http.StatusOK, + JSON: resp, } - // TODO: Return 502 if the remote server errored. - // TODO: Return 504 if the remote server timed out. - return httputil.LogThenError(req, err) } } return util.JSONResponse{ - Code: http.StatusOK, - JSON: resp, + Code: http.StatusNotFound, + JSON: jsonerror.NotFound( + fmt.Sprintf("Room alias %s not found", roomAlias), + ), } } +// getRoomIDForAlias queries the roomserver API and returns a Directory Response +// on a successful query +func getRoomIDForAlias( + req *http.Request, + rsAPI roomserverAPI.RoomserverAliasAPI, + roomAlias string, +) (resp gomatrixserverlib.RespDirectory, err error) { + // Query the roomserver API to check if the alias exists locally + queryReq := roomserverAPI.GetRoomIDForAliasRequest{Alias: roomAlias} + var queryRes roomserverAPI.GetRoomIDForAliasResponse + if err = rsAPI.GetRoomIDForAlias(req.Context(), &queryReq, &queryRes); err != nil { + return + } + return gomatrixserverlib.RespDirectory{ + RoomID: queryRes.RoomID, + Servers: []gomatrixserverlib.ServerName{}, + }, nil +} + // SetLocalAlias implements PUT /directory/room/{roomAlias} // TODO: Check if the user has the power level to set an alias func SetLocalAlias( @@ -96,7 +139,7 @@ func SetLocalAlias( device *authtypes.Device, alias string, cfg *config.Dendrite, - aliasAPI api.RoomserverAliasAPI, + aliasAPI roomserverAPI.RoomserverAliasAPI, ) util.JSONResponse { _, domain, err := gomatrixserverlib.SplitID('#', alias) if err != nil { @@ -138,12 +181,12 @@ func SetLocalAlias( return *resErr } - queryReq := api.SetRoomAliasRequest{ + queryReq := roomserverAPI.SetRoomAliasRequest{ UserID: device.UserID, RoomID: r.RoomID, Alias: alias, } - var queryRes api.SetRoomAliasResponse + var queryRes roomserverAPI.SetRoomAliasResponse if err := aliasAPI.SetRoomAlias(req.Context(), &queryReq, &queryRes); err != nil { return httputil.LogThenError(req, err) } @@ -167,13 +210,13 @@ func RemoveLocalAlias( req *http.Request, device *authtypes.Device, alias string, - aliasAPI api.RoomserverAliasAPI, + aliasAPI roomserverAPI.RoomserverAliasAPI, ) util.JSONResponse { - queryReq := api.RemoveRoomAliasRequest{ + queryReq := roomserverAPI.RemoveRoomAliasRequest{ Alias: alias, UserID: device.UserID, } - var queryRes api.RemoveRoomAliasResponse + var queryRes roomserverAPI.RemoveRoomAliasResponse if err := aliasAPI.RemoveRoomAlias(req.Context(), &queryReq, &queryRes); err != nil { return httputil.LogThenError(req, err) } 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 0314861b0..145484e1e 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -20,6 +20,7 @@ import ( "strings" "github.com/gorilla/mux" + appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/auth" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/storage/accounts" @@ -29,7 +30,7 @@ import ( "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/common/config" "github.com/matrix-org/dendrite/common/transactions" - "github.com/matrix-org/dendrite/roomserver/api" + roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" ) @@ -42,8 +43,8 @@ const pathPrefixUnstable = "/_matrix/client/unstable" // to clients which need to make outbound HTTP requests. func Setup( apiMux *mux.Router, cfg config.Dendrite, - producer *producers.RoomserverProducer, queryAPI api.RoomserverQueryAPI, - aliasAPI api.RoomserverAliasAPI, + producer *producers.RoomserverProducer, queryAPI roomserverAPI.RoomserverQueryAPI, + appserviceAPI appserviceAPI.AppServiceQueryAPI, aliasAPI roomserverAPI.RoomserverAliasAPI, accountDB *accounts.Database, deviceDB *devices.Database, federation *gomatrixserverlib.FederationClient, @@ -142,9 +143,9 @@ func Setup( })).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/directory/room/{roomAlias}", - common.MakeAuthAPI("directory_room", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + common.MakeExternalAPI("directory_room", func(req *http.Request) util.JSONResponse { vars := mux.Vars(req) - return DirectoryRoom(req, vars["roomAlias"], federation, &cfg, aliasAPI) + return DirectoryRoom(req, vars["roomAlias"], federation, &cfg, aliasAPI, appserviceAPI) }), ).Methods(http.MethodGet, http.MethodOptions) diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go index 619882456..b5f6c9da5 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go @@ -35,11 +35,12 @@ func main() { alias, input, query := base.CreateHTTPRoomserverAPIs() typingInputAPI := base.CreateHTTPTypingServerAPIs() + asQuery := base.CreateHTTPAppServiceAPIs() cache := transactions.New() clientapi.SetupClientAPIComponent( base, deviceDB, accountDB, federation, &keyRing, - alias, input, query, typingInputAPI, cache, + alias, input, query, typingInputAPI, asQuery, cache, ) base.SetupAndServeHTTP(string(base.Cfg.Listen.ClientAPI)) diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go index b1ad0910b..8cc73e271 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go @@ -58,9 +58,13 @@ func main() { alias, input, query := roomserver.SetupRoomServerComponent(base) typingInputAPI := typingserver.SetupTypingServerComponent(base, cache.NewTypingCache()) + asQuery := appservice.SetupAppServiceAPIComponent( + base, accountDB, deviceDB, federation, alias, query, transactions.New(), + ) + clientapi.SetupClientAPIComponent( base, deviceDB, accountDB, - federation, &keyRing, alias, input, query, typingInputAPI, + federation, &keyRing, alias, input, query, typingInputAPI, asQuery, transactions.New(), ) federationapi.SetupFederationAPIComponent(base, accountDB, deviceDB, federation, &keyRing, alias, input, query) @@ -68,7 +72,6 @@ func main() { mediaapi.SetupMediaAPIComponent(base, deviceDB) publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB) syncapi.SetupSyncAPIComponent(base, deviceDB, accountDB, query) - appservice.SetupAppServiceAPIComponent(base, accountDB, deviceDB, federation, alias, query, transactions.New()) httpHandler := common.WrapHandlerInCORS(base.APIMux) diff --git a/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go b/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go index a00ad027d..e918d4436 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go +++ b/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go @@ -44,7 +44,7 @@ type RoomserverAliasAPIDatabase interface { RemoveRoomAlias(ctx context.Context, alias string) error } -// RoomserverAliasAPI is an implementation of api.RoomserverAliasAPI +// RoomserverAliasAPI is an implementation of alias.RoomserverAliasAPI type RoomserverAliasAPI struct { DB RoomserverAliasAPIDatabase Cfg *config.Dendrite @@ -52,7 +52,7 @@ type RoomserverAliasAPI struct { QueryAPI api.RoomserverQueryAPI } -// SetRoomAlias implements api.RoomserverAliasAPI +// SetRoomAlias implements alias.RoomserverAliasAPI func (r *RoomserverAliasAPI) SetRoomAlias( ctx context.Context, request *api.SetRoomAliasRequest, @@ -82,7 +82,7 @@ func (r *RoomserverAliasAPI) SetRoomAlias( return r.sendUpdatedAliasesEvent(context.TODO(), request.UserID, request.RoomID) } -// GetRoomIDForAlias implements api.RoomserverAliasAPI +// GetRoomIDForAlias implements alias.RoomserverAliasAPI func (r *RoomserverAliasAPI) GetRoomIDForAlias( ctx context.Context, request *api.GetRoomIDForAliasRequest, @@ -98,7 +98,7 @@ func (r *RoomserverAliasAPI) GetRoomIDForAlias( return nil } -// GetAliasesForRoomID implements api.RoomserverAliasAPI +// GetAliasesForRoomID implements alias.RoomserverAliasAPI func (r *RoomserverAliasAPI) GetAliasesForRoomID( ctx context.Context, request *api.GetAliasesForRoomIDRequest, @@ -114,7 +114,7 @@ func (r *RoomserverAliasAPI) GetAliasesForRoomID( return nil } -// RemoveRoomAlias implements api.RoomserverAliasAPI +// RemoveRoomAlias implements alias.RoomserverAliasAPI func (r *RoomserverAliasAPI) RemoveRoomAlias( ctx context.Context, request *api.RemoveRoomAliasRequest,