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 cb7fb9854..df4a936d3 100644 --- a/src/github.com/matrix-org/dendrite/appservice/api/query.go +++ b/src/github.com/matrix-org/dendrite/appservice/api/query.go @@ -23,12 +23,11 @@ import ( "errors" "net/http" + "github.com/matrix-org/dendrite/appservice/types" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/storage/accounts" - "github.com/matrix-org/gomatrixserverlib" - - "github.com/matrix-org/dendrite/appservice/types" commonHTTP "github.com/matrix-org/dendrite/common/http" + "github.com/matrix-org/gomatrixserverlib" opentracing "github.com/opentracing/opentracing-go" ) @@ -75,7 +74,7 @@ type GetProtocolDefinitionRequest struct { // GetProtocolDefinitionResponse is a response providing a protocol definition // for the given protocol ID type GetProtocolDefinitionResponse struct { - ProtocolDefinition string `json:"protocol_definition"` + ProtocolDefinition gomatrixserverlib.RawJSON `json:"protocol_definition"` } // GetAllProtocolDefinitionsRequest is a request to the appservice component @@ -89,6 +88,22 @@ type GetAllProtocolDefinitionsResponse struct { Protocols types.ThirdPartyProtocols `json:"protocols"` } +// ThirdPartyProxyRequest is a request from a client to a third party lookup +// endpoint on an application service. The job of the homeserver is simply to +// proxy the request, thus the same request/response format can be use for each +// third party lookup endpoint. +type ThirdPartyProxyRequest struct { + ProtocolID string `json:"protocol_id"` + Path string `json:"path"` + Content gomatrixserverlib.RawJSON `json:"content"` +} + +// ThirdPartyProxyResponse is a response from an application service to a client +// about a third party lookup request. +type ThirdPartyProxyResponse struct { + Content gomatrixserverlib.RawJSON `json:"content"` +} + // AppServiceQueryAPI is used to query user and room alias data from application // services type AppServiceQueryAPI interface { @@ -116,6 +131,11 @@ type AppServiceQueryAPI interface { req *GetAllProtocolDefinitionsRequest, response *GetAllProtocolDefinitionsResponse, ) error + ThirdPartyProxy( + ctx context.Context, + req *ThirdPartyProxyRequest, + response *ThirdPartyProxyResponse, + ) error } // RoomAliasExistsPath is the HTTP path for the RoomAliasExists API @@ -130,6 +150,9 @@ const GetAllProtocolDefinitionsPath = "/api/appservice/GetAllProtocolDefinitions // AppServiceUserIDExistsPath is the HTTP path for the UserIDExists API const AppServiceUserIDExistsPath = "/api/appservice/UserIDExists" +// ThirdPartyProxyPath is the HTTP path for the ThirdPartyProxy API +const ThirdPartyProxyPath = "/api/appservice/ThirdPartyProxy" + // httpAppServiceQueryAPI contains the URL to an appservice query API and a // reference to a httpClient used to reach it type httpAppServiceQueryAPI struct { @@ -244,3 +267,16 @@ func RetreiveUserProfile( // profile should not be nil at this point return profile, nil } + +// ThirdPartyProxy implements AppServiceQueryAPI +func (h *httpAppServiceQueryAPI) ThirdPartyProxy( + ctx context.Context, + request *ThirdPartyProxyRequest, + response *ThirdPartyProxyResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "appserviceThirdPartyProxy") + defer span.Finish() + + apiURL := h.appserviceURL + ThirdPartyProxyPath + 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 71ab77a04..7d6bbec64 100644 --- a/src/github.com/matrix-org/dendrite/appservice/appservice.go +++ b/src/github.com/matrix-org/dendrite/appservice/appservice.go @@ -133,7 +133,10 @@ func setupWorkers( workerStates []types.ApplicationServiceWorkerState, ) { // Clear all old protocol definitions on startup - appserviceDB.ClearProtocolDefinitions(context.TODO()) + err := appserviceDB.ClearProtocolDefinitions(context.TODO()) + if err != nil { + log.WithError(err).Fatalf("unable to clear appservice protocol definitions from db") + } // Create a worker that handles transmitting events to a single homeserver for _, workerState := range workerStates { 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 2c436a2ce..ef4e04c91 100644 --- a/src/github.com/matrix-org/dendrite/appservice/query/query.go +++ b/src/github.com/matrix-org/dendrite/appservice/query/query.go @@ -17,23 +17,31 @@ package query import ( + "bytes" "context" + "database/sql" "encoding/json" + "fmt" + "io/ioutil" "net/http" "net/url" + "sync" "time" "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/appservice/storage" "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" opentracing "github.com/opentracing/opentracing-go" log "github.com/sirupsen/logrus" ) -const roomAliasExistsPath = "/rooms/" -const userIDExistsPath = "/users/" +const ( + roomAliasExistsPath = "/rooms/" + userIDExistsPath = "/users/" +) // AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI type AppServiceQueryAPI struct { @@ -42,44 +50,6 @@ type AppServiceQueryAPI struct { Db *storage.Database } -// GetProtocolDefinition queries the database for the protocol definition of a -// protocol with given ID -func (a *AppServiceQueryAPI) GetProtocolDefinition( - ctx context.Context, - request *api.GetProtocolDefinitionRequest, - response *api.GetProtocolDefinitionResponse, -) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "ApplicationServiceGetProtocolDefinition") - defer span.Finish() - - protocolDefinition, err := a.Db.GetProtocolDefinition(ctx, request.ProtocolID) - if err != nil { - return err - } - - response.ProtocolDefinition = protocolDefinition - return nil -} - -// GetAllProtocolDefinitions queries the database for all known protocol -// definitions and their IDs -func (a *AppServiceQueryAPI) GetAllProtocolDefinitions( - ctx context.Context, - request *api.GetAllProtocolDefinitionsRequest, - response *api.GetAllProtocolDefinitionsResponse, -) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "ApplicationServiceGetAllProtocolDefinitions") - defer span.Finish() - - protocolDefinitions, err := a.Db.GetAllProtocolDefinitions(ctx) - if err != nil { - return err - } - - response.Protocols = protocolDefinitions - return nil -} - // RoomAliasExists performs a request to '/room/{roomAlias}' on all known // handling application services until one admits to owning the room func (a *AppServiceQueryAPI) RoomAliasExists( @@ -100,6 +70,10 @@ func (a *AppServiceQueryAPI) RoomAliasExists( if appservice.URL != "" && appservice.IsInterestedInRoomAlias(request.Alias) { // The full path to the rooms API, includes hs token URL, err := url.Parse(appservice.URL + roomAliasExistsPath) + if err != nil { + return err + } + URL.Path += request.Alias apiURL := URL.String() + "?access_token=" + appservice.HSToken @@ -110,15 +84,14 @@ func (a *AppServiceQueryAPI) RoomAliasExists( return err } req = req.WithContext(ctx) - resp, err := a.HTTPClient.Do(req) if resp != nil { defer func() { err = resp.Body.Close() if err != nil { log.WithFields(log.Fields{ - "appservice_id": appservice.ID, - "status_code": resp.StatusCode, + "appservice": appservice.ID, + "status_code": resp.StatusCode, }).WithError(err).Error("Unable to close application service response body") } }() @@ -137,8 +110,8 @@ func (a *AppServiceQueryAPI) RoomAliasExists( default: // Application service reported an error. Warn log.WithFields(log.Fields{ - "appservice_id": appservice.ID, - "status_code": resp.StatusCode, + "appservice": appservice.ID, + "status_code": resp.StatusCode, }).Warn("Application service responded with non-OK status code") } } @@ -177,34 +150,30 @@ func (a *AppServiceQueryAPI) UserIDExists( if err != nil { return err } - resp, err := a.HTTPClient.Do(req.WithContext(ctx)) + req = req.WithContext(ctx) + + // Make a request to the application service + resp, err := a.HTTPClient.Do(req) 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") - } - }() + defer resp.Body.Close() // nolint: errcheck } if err != nil { log.WithFields(log.Fields{ - "appservice_id": appservice.ID, + "appservice": appservice.ID, }).WithError(err).Error("issue querying user ID on application service") return err } + if resp.StatusCode == http.StatusOK { // StatusOK received from appservice. User ID exists response.UserIDExists = true return nil } - // Log non OK + // Log if return code is not OK log.WithFields(log.Fields{ - "appservice_id": appservice.ID, - "status_code": resp.StatusCode, + "appservice": appservice.ID, + "status_code": resp.StatusCode, }).Warn("application service responded with non-OK status code") } } @@ -213,6 +182,180 @@ func (a *AppServiceQueryAPI) UserIDExists( return nil } +// GetProtocolDefinition queries the database for the protocol definition of a +// protocol with given ID +func (a *AppServiceQueryAPI) GetProtocolDefinition( + ctx context.Context, + request *api.GetProtocolDefinitionRequest, + response *api.GetProtocolDefinitionResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "ApplicationServiceGetProtocolDefinition") + defer span.Finish() + + protocolDefinition, err := a.Db.GetProtocolDefinition(ctx, request.ProtocolID) + if err != nil && err != sql.ErrNoRows { + return err + } + + response.ProtocolDefinition = gomatrixserverlib.RawJSON(protocolDefinition) + return nil +} + +// GetAllProtocolDefinitions queries the database for all known protocol +// definitions and their IDs +func (a *AppServiceQueryAPI) GetAllProtocolDefinitions( + ctx context.Context, + request *api.GetAllProtocolDefinitionsRequest, + response *api.GetAllProtocolDefinitionsResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "ApplicationServiceGetAllProtocolDefinitions") + defer span.Finish() + + protocolDefinitions, err := a.Db.GetAllProtocolDefinitions(ctx) + if err != nil { + return err + } + + response.Protocols = protocolDefinitions + return nil +} + +// ThirdPartyProxy will proxy a request for third party lookup information from +// a client to a connected application service. If a protocol ID is specified, +// the application service that handles it will solely be contacted, otherwise +// all application services will be contacted and their responses concatenated +func (a *AppServiceQueryAPI) ThirdPartyProxy( + ctx context.Context, + request *api.ThirdPartyProxyRequest, + response *api.ThirdPartyProxyResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "ApplicationServiceGetAllProtocolDefinitions") + defer span.Finish() + + // Create an HTTP client if one does not already exist + if a.HTTPClient == nil { + a.HTTPClient = makeHTTPClient() + } + + appservicesToContact := make([]config.ApplicationService, 0, len(a.Cfg.Derived.ApplicationServices)) + if request.ProtocolID == "" { + // If no protocol ID was specified, send the request to all known application + // services in parallel and stitch together the results + appservicesToContact = a.Cfg.Derived.ApplicationServices + } else { + // Otherwise simply proxy the request to the application services handling + // this protocol and return the result + for _, as := range a.Cfg.Derived.ApplicationServices { + for _, protocol := range as.Protocols { + if request.ProtocolID == protocol { + appservicesToContact = append(appservicesToContact, as) + break + } + } + } + } + + // Contact each application service in parallel, wait for all to finish before continuing + var wg sync.WaitGroup + var err error + responseSlice := make([]interface{}, 0) + for _, appservice := range appservicesToContact { + // Increment goroutine waitgroup counter + wg.Add(1) + + go func(ctx context.Context, + h *http.Client, + req *api.ThirdPartyProxyRequest, + as config.ApplicationService, + r *[]interface{}, + ) { + // Decrement waitgroup counter once the request has finished + defer wg.Done() + + // Contact the application service, return nil or error if something went wrong + contactApplicationService(ctx, h, req, as, r) + }(ctx, a.HTTPClient, request, appservice, &responseSlice) + } + + // Wait for all requests to finish + wg.Wait() + + // Convert the slice of responses back to JSON + response.Content, err = json.Marshal(responseSlice) + return err +} + +// contactApplicationService proxies a third party lookup request to an +// application service +func contactApplicationService( + ctx context.Context, + httpClient *http.Client, + request *api.ThirdPartyProxyRequest, + as config.ApplicationService, + responseSlice *[]interface{}, +) { + // Build the request with body and parameters to the application service + if as.URL == "" { + return + } + + // Send a request to each application service. If one responds that it has + // created the room, immediately return. + requestURL := as.URL + request.Path + req, err := http.NewRequest(http.MethodGet, requestURL, bytes.NewReader(request.Content)) + if err != nil { + log.WithFields(log.Fields{ + "appservice": as.ID, + }).WithError(err).Error("problem building proxy request to application service") + return + } + req = req.WithContext(ctx) + + // Make a request to the application service + resp, err := httpClient.Do(req) + if resp != nil { + defer resp.Body.Close() // nolint: errcheck + } + if err != nil { + log.WithFields(log.Fields{ + "appservice": as.ID, + }).WithError(err).Warn("unable to proxy request to application service") + return + } + + if resp.StatusCode != http.StatusOK { + log.WithFields(log.Fields{ + "appservice": as.ID, + "status_code": resp.StatusCode, + }).Warn("non-OK response from application server while proxying request") + return + } + + // Unmarshal response body into a generic slice and append to the slice of + // existing responses, to eventually be returned to the client + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.WithFields(log.Fields{ + "appservice": as.ID, + }).WithError(err).Warn("unable to read response from application server while proxying request") + return + } + + // Temporary slice to unmarshal into + querySlice := make([]interface{}, 0) + err = json.Unmarshal(body, &querySlice) + if err != nil { + log.WithFields(log.Fields{ + "appservice": as.ID, + }).WithError(err).Warn("unable to unmarshal response from application server while proxying request") + return + } + + // Append to existing responses + fmt.Println("Adding", querySlice) + *responseSlice = append(*responseSlice, querySlice...) +} + // makeHTTPClient creates an HTTP client with certain options that will be used for all query requests to application services func makeHTTPClient() *http.Client { return &http.Client{ @@ -279,4 +422,21 @@ func (a *AppServiceQueryAPI) SetupHTTP(servMux *http.ServeMux) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + servMux.Handle( + api.ThirdPartyProxyPath, + common.MakeInternalAPI("appserviceThirdPartyProxy", func(req *http.Request) util.JSONResponse { + var request api.ThirdPartyProxyRequest + var response api.ThirdPartyProxyResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := a.ThirdPartyProxy(req.Context(), &request, &response); err != nil { + if err == sql.ErrNoRows { + return util.JSONResponse{Code: http.StatusNotFound, JSON: &response} + } + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) } diff --git a/src/github.com/matrix-org/dendrite/appservice/routing/routing.go b/src/github.com/matrix-org/dendrite/appservice/routing/routing.go index f0b8461dc..c276b586f 100644 --- a/src/github.com/matrix-org/dendrite/appservice/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/appservice/routing/routing.go @@ -27,7 +27,12 @@ import ( "github.com/matrix-org/util" ) -const pathPrefixApp = "/_matrix/app/r0" +const ( + // PathPrefixApp for current stable application services API version + PathPrefixApp = "/_matrix/app/r0" + // PathPrefixAppUnstable is the unstable application services API version + PathPrefixAppUnstable = "/_matrix/app/unstable" +) // Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client // to clients which need to make outbound HTTP requests. @@ -38,7 +43,7 @@ func Setup( federation *gomatrixserverlib.FederationClient, // nolint: unparam transactionsCache *transactions.Cache, // nolint: unparam ) { - appMux := apiMux.PathPrefix(pathPrefixApp).Subrouter() + appMux := apiMux.PathPrefix(PathPrefixApp).Subrouter() appMux.Handle("/alias", common.MakeExternalAPI("alias", func(req *http.Request) util.JSONResponse { diff --git a/src/github.com/matrix-org/dendrite/appservice/storage/storage.go b/src/github.com/matrix-org/dendrite/appservice/storage/storage.go index 50299d3d8..fe8eda94d 100644 --- a/src/github.com/matrix-org/dendrite/appservice/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/appservice/storage/storage.go @@ -139,7 +139,7 @@ func (d *Database) StoreProtocolDefinition( return d.thirdparty.insertProtocolDefinition(ctx, protocolID, protocolDefinition) } -// ClearProtocolDefinition clears all protocol definition entries in the +// ClearProtocolDefinitions clears all protocol definition entries in the // database. This is done on each startup to wipe old protocol definitions from // previous application services. func (d *Database) ClearProtocolDefinitions( diff --git a/src/github.com/matrix-org/dendrite/appservice/storage/third_party_table.go b/src/github.com/matrix-org/dendrite/appservice/storage/third_party_table.go index 18d4f63c2..57d2204bc 100644 --- a/src/github.com/matrix-org/dendrite/appservice/storage/third_party_table.go +++ b/src/github.com/matrix-org/dendrite/appservice/storage/third_party_table.go @@ -26,8 +26,10 @@ import ( const thirdPartySchema = ` -- Stores protocol definitions for clients to later request CREATE TABLE IF NOT EXISTS appservice_third_party_protocol_def ( - -- The ID of the procotol + -- The ID of the protocol protocol_id TEXT NOT NULL PRIMARY KEY, + -- The URL of the application service handling this protocol + appservice_url TEXT NOT NULL, -- The JSON-encoded protocol definition protocol_definition TEXT NOT NULL, UNIQUE(protocol_id) @@ -106,6 +108,7 @@ func (s *thirdPartyStatements) selectAllProtocolDefinitions( ctx context.Context, ) (protocols types.ThirdPartyProtocols, err error) { protocolDefinitionRows, err := s.selectAllProtocolDefinitionsStmt.QueryContext(ctx) + protocols = make(types.ThirdPartyProtocols) if err != nil && err != sql.ErrNoRows { return } @@ -116,12 +119,14 @@ func (s *thirdPartyStatements) selectAllProtocolDefinitions( } }() + // Extract protocol information from each row for protocolDefinitionRows.Next() { var protocolID, protocolDefinition string if err = protocolDefinitionRows.Scan(&protocolID, &protocolDefinition); err != nil { return nil, err } + // Store each protocol in a map indexed by protocol ID protocols[protocolID] = gomatrixserverlib.RawJSON(protocolDefinition) } @@ -129,7 +134,7 @@ func (s *thirdPartyStatements) selectAllProtocolDefinitions( } // insertProtocolDefinition inserts a protocol ID along with its definition in -// order for clients to later retreive it from the client-server API. +// order for clients to later retrieve it from the client-server API. func (s *thirdPartyStatements) insertProtocolDefinition( ctx context.Context, protocolID, protocolDefinition string, diff --git a/src/github.com/matrix-org/dendrite/appservice/workers/third_party.go b/src/github.com/matrix-org/dendrite/appservice/workers/third_party.go index cd6916745..27b96a5ac 100644 --- a/src/github.com/matrix-org/dendrite/appservice/workers/third_party.go +++ b/src/github.com/matrix-org/dendrite/appservice/workers/third_party.go @@ -79,7 +79,13 @@ func ThirdPartyWorker( } // Cache protocol definition for clients to request later - storeProtocolDefinition(ctx, db, appservice, protocolID, protocolDefinition) + err = storeProtocolDefinition(ctx, db, protocolID, protocolDefinition) + if err != nil { + log.WithFields(log.Fields{ + "appservice": appservice.ID, + "definition": protocolDefinition, + }).WithError(err).Fatalf("unable to store appservice protocol definition in db") + } } } @@ -144,7 +150,6 @@ func retreiveProtocolInformation( func storeProtocolDefinition( ctx context.Context, db *storage.Database, - appservice config.ApplicationService, protocolID, protocolDefinition string, ) error { return db.StoreProtocolDefinition(ctx, protocolID, protocolDefinition) diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go b/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go index a5a9b2bf9..6857775d2 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go @@ -158,6 +158,7 @@ func verifyAccessToken(req *http.Request, deviceDB DeviceDatabase) (device *auth } return } + fmt.Println("Got token:", token) device, err = deviceDB.GetDeviceByAccessToken(req.Context(), token) if err != nil { if err == sql.ErrNoRows { 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 ab696a179..c57ba0d09 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -35,9 +35,14 @@ import ( "github.com/matrix-org/util" ) -const pathPrefixV1 = "/_matrix/client/api/v1" -const pathPrefixR0 = "/_matrix/client/r0" -const pathPrefixUnstable = "/_matrix/client/unstable" +const ( + // PathPrefixClientAPI is the deprecated client server API version + PathPrefixClientAPI = "/_matrix/client/api/v1" + // PathPrefixClient is the current stable client server API version + PathPrefixClient = "/_matrix/client/r0" + // PathPrefixClientUnstable is the unstable client server API version + PathPrefixClientUnstable = "/_matrix/client/unstable" +) // Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client // to clients which need to make outbound HTTP requests. @@ -56,7 +61,6 @@ func Setup( typingProducer *producers.TypingServerProducer, transactionsCache *transactions.Cache, ) { - apiMux.Handle("/_matrix/client/versions", common.MakeExternalAPI("versions", func(req *http.Request) util.JSONResponse { return util.JSONResponse{ @@ -73,9 +77,9 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter() - v1mux := apiMux.PathPrefix(pathPrefixV1).Subrouter() - unstableMux := apiMux.PathPrefix(pathPrefixUnstable).Subrouter() + r0mux := apiMux.PathPrefix(PathPrefixClient).Subrouter() + v1mux := apiMux.PathPrefix(PathPrefixClientAPI).Subrouter() + unstableMux := apiMux.PathPrefix(PathPrefixClientUnstable).Subrouter() authData := auth.Data{accountDB, deviceDB, cfg.Derived.ApplicationServices} @@ -313,16 +317,6 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - unstableMux.Handle("/thirdparty/protocols", - common.MakeExternalAPI("thirdparty_protocols", func(req *http.Request) util.JSONResponse { - // TODO: Return the third party protcols - return util.JSONResponse{ - Code: http.StatusOK, - JSON: struct{}{}, - } - }), - ).Methods(http.MethodGet, http.MethodOptions) - r0mux.Handle("/rooms/{roomID}/initialSync", common.MakeExternalAPI("rooms_initial_sync", func(req *http.Request) util.JSONResponse { // TODO: Allow people to peek into rooms. @@ -390,18 +384,44 @@ func Setup( // Third party lookups r0mux.Handle("/thirdparty/protocol/{protocolID}", - common.MakeExternalAPI("get_protocols", func(req *http.Request) util.JSONResponse { + common.MakeAuthAPI("third_party_protocols", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse { vars := mux.Vars(req) return GetThirdPartyProtocol(req, asAPI, vars["protocolID"]) }), ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/thirdparty/protocols", - common.MakeExternalAPI("get_protocols", func(req *http.Request) util.JSONResponse { + common.MakeAuthAPI("third_party_protocol", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse { return GetThirdPartyProtocols(req, asAPI) }), ).Methods(http.MethodGet, http.MethodOptions) + r0mux.Handle("/thirdparty/location/{protocolID}", + common.MakeAuthAPI("third_party_protocol", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + vars := mux.Vars(req) + return ThirdPartyProxy(req, asAPI, vars["protocolID"]) + }), + ).Methods(http.MethodGet, http.MethodOptions) + + r0mux.Handle("/thirdparty/user/{protocolID}", + common.MakeAuthAPI("third_party_protocol", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + vars := mux.Vars(req) + return ThirdPartyProxy(req, asAPI, vars["protocolID"]) + }), + ).Methods(http.MethodGet, http.MethodOptions) + + r0mux.Handle("/thirdparty/location", + common.MakeAuthAPI("third_party_protocol", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + return ThirdPartyProxy(req, asAPI, "") + }), + ).Methods(http.MethodGet, http.MethodOptions) + + r0mux.Handle("/thirdparty/user", + common.MakeAuthAPI("third_party_protocol", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + return ThirdPartyProxy(req, asAPI, "") + }), + ).Methods(http.MethodGet, http.MethodOptions) + // Stub implementations for sytest r0mux.Handle("/events", common.MakeExternalAPI("events", func(req *http.Request) util.JSONResponse { diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/third_party.go b/src/github.com/matrix-org/dendrite/clientapi/routing/third_party.go index 5630d743b..768098f9d 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/third_party.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/third_party.go @@ -15,11 +15,14 @@ package routing import ( - "encoding/json" + "io/ioutil" "net/http" + "strings" appserviceAPI "github.com/matrix-org/dendrite/appservice/api" + appserviceRouting "github.com/matrix-org/dendrite/appservice/routing" "github.com/matrix-org/dendrite/clientapi/httputil" + "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/util" ) @@ -39,6 +42,14 @@ func GetThirdPartyProtocol( return httputil.LogThenError(req, err) } + // Account for unknown protocol/empty definition + if len(queryRes.ProtocolDefinition) == 0 { + return util.JSONResponse{ + Code: http.StatusNotFound, + JSON: jsonerror.NotFound("unknown protocol"), + } + } + return util.JSONResponse{ Code: http.StatusOK, JSON: queryRes.ProtocolDefinition, @@ -60,15 +71,44 @@ func GetThirdPartyProtocols( // TODO: Check what we get if no protocols defined by anyone - // Marshal protocols to JSON - protocolJSON, err := json.Marshal(queryRes.Protocols) + // Return protocol IDs along with definitions + return util.JSONResponse{ + Code: http.StatusOK, + JSON: queryRes.Protocols, + } +} + +// ThirdPartyProxy proxies a third party lookup request to the handler +// application service +func ThirdPartyProxy( + req *http.Request, + asAPI appserviceAPI.AppServiceQueryAPI, + protocolID string, +) util.JSONResponse { + // Read the request body + body, err := ioutil.ReadAll(req.Body) if err != nil { return httputil.LogThenError(req, err) } - // Return protocol IDs along with definitions + // Rewrite the path from a client URL to an application service URL + requestPath := strings.Replace(req.URL.String(), PathPrefixClient, appserviceRouting.PathPrefixAppUnstable, 1) + + // Proxy the location lookup to the appservices component, which will send it + // off to an application service + queryReq := appserviceAPI.ThirdPartyProxyRequest{ + ProtocolID: protocolID, + Path: requestPath, + Content: body, + } + var queryRes appserviceAPI.ThirdPartyProxyResponse + if err := asAPI.ThirdPartyProxy(req.Context(), &queryReq, &queryRes); err != nil { + return httputil.LogThenError(req, err) + } + + // Return response to the client return util.JSONResponse{ Code: http.StatusOK, - JSON: protocolJSON, + JSON: queryRes.Content, } } diff --git a/src/github.com/matrix-org/dendrite/common/config/appservice.go b/src/github.com/matrix-org/dendrite/common/config/appservice.go index 5a54be3ff..95e9212c3 100644 --- a/src/github.com/matrix-org/dendrite/common/config/appservice.go +++ b/src/github.com/matrix-org/dendrite/common/config/appservice.go @@ -231,8 +231,8 @@ func checkErrors(config *Dendrite) (err error) { // Namespace-related checks for key, namespaceSlice := range appservice.NamespaceMap { for _, namespace := range namespaceSlice { - if err := validateNamespace(&appservice, key, &namespace, groupIDRegexp); err != nil { - return err + if err = validateNamespace(&appservice, key, &namespace, groupIDRegexp); err != nil { + return } } } @@ -240,7 +240,7 @@ func checkErrors(config *Dendrite) (err error) { // Check if the url has trailing /'s. If so, remove them appservice.URL = strings.TrimRight(appservice.URL, "/") if err = duplicationCheck(appservice, &idMap, &tokenMap, &protocolMap); err != nil { - return err + return } // TODO: Remove once rate_limited is implemented diff --git a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go index c7c0f7e21..f061fb1d5 100644 --- a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go @@ -30,8 +30,10 @@ import ( ) const ( - pathPrefixV2Keys = "/_matrix/key/v2" - pathPrefixV1Federation = "/_matrix/federation/v1" + // PathPrefixKeys is the current Key API version + PathPrefixKeys = "/_matrix/key/v2" + // PathPrefixFederation is the current Federation API version + PathPrefixFederation = "/_matrix/federation/v1" ) // Setup registers HTTP handlers with the given ServeMux. @@ -47,8 +49,8 @@ func Setup( accountDB *accounts.Database, deviceDB *devices.Database, ) { - v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter() - v1fedmux := apiMux.PathPrefix(pathPrefixV1Federation).Subrouter() + v2keysmux := apiMux.PathPrefix(PathPrefixKeys).Subrouter() + v1fedmux := apiMux.PathPrefix(PathPrefixFederation).Subrouter() localKeys := common.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse { return LocalKeys(cfg) diff --git a/src/github.com/matrix-org/dendrite/mediaapi/routing/routing.go b/src/github.com/matrix-org/dendrite/mediaapi/routing/routing.go index 88e5bd126..4e19f60f0 100644 --- a/src/github.com/matrix-org/dendrite/mediaapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/mediaapi/routing/routing.go @@ -31,7 +31,10 @@ import ( "github.com/prometheus/client_golang/prometheus" ) -const pathPrefixR0 = "/_matrix/media/v1" +const ( + // PathPrefixMedia is the current Media API version + PathPrefixMedia = "/_matrix/media/v1" +) // Setup registers the media API HTTP handlers func Setup( @@ -41,7 +44,7 @@ func Setup( deviceDB *devices.Database, client *gomatrixserverlib.Client, ) { - r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter() + r0mux := apiMux.PathPrefix(PathPrefixMedia).Subrouter() activeThumbnailGeneration := &types.ActiveThumbnailGeneration{ PathToResult: map[string]*types.ThumbnailGenerationResult{}, diff --git a/src/github.com/matrix-org/dendrite/publicroomsapi/routing/routing.go b/src/github.com/matrix-org/dendrite/publicroomsapi/routing/routing.go index 804f8cce6..3f2f611a2 100644 --- a/src/github.com/matrix-org/dendrite/publicroomsapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/publicroomsapi/routing/routing.go @@ -27,12 +27,14 @@ import ( "github.com/matrix-org/util" ) -const pathPrefixR0 = "/_matrix/client/r0" +const ( + // PathPrefixClientPublicRooms is the current Client-Server Public Rooms API version + PathPrefixClientPublicRooms = "/_matrix/client/r0" +) // Setup configures the given mux with publicroomsapi server listeners func Setup(apiMux *mux.Router, deviceDB *devices.Database, publicRoomsDB *storage.PublicRoomsServerDatabase) { - r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter() - + r0mux := apiMux.PathPrefix(PathPrefixClientPublicRooms).Subrouter() authData := auth.Data{nil, deviceDB, nil} r0mux.Handle("/directory/list/room/{roomID}", diff --git a/src/github.com/matrix-org/dendrite/syncapi/routing/routing.go b/src/github.com/matrix-org/dendrite/syncapi/routing/routing.go index 0671eca8e..624ac90da 100644 --- a/src/github.com/matrix-org/dendrite/syncapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/syncapi/routing/routing.go @@ -27,11 +27,14 @@ import ( "github.com/matrix-org/util" ) -const pathPrefixR0 = "/_matrix/client/r0" +const ( + // PathPrefixClientSync is the current Client-Server Sync API version + PathPrefixClientSync = "/_matrix/client/r0" +) // Setup configures the given mux with sync-server listeners func Setup(apiMux *mux.Router, srp *sync.RequestPool, syncDB *storage.SyncServerDatabase, deviceDB *devices.Database) { - r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter() + r0mux := apiMux.PathPrefix(PathPrefixClientSync).Subrouter() authData := auth.Data{nil, deviceDB, nil}