AppServices: Implement /users/{userID} (#521)
* Add support for querying /users/ on appservices * Fix copy/paste errormain
parent
609646c19b
commit
bab4d1401f
|
@ -19,8 +19,14 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
commonHTTP "github.com/matrix-org/dendrite/common/http"
|
commonHTTP "github.com/matrix-org/dendrite/common/http"
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
)
|
)
|
||||||
|
@ -38,6 +44,27 @@ type RoomAliasExistsResponse struct {
|
||||||
AliasExists bool `json:"exists"`
|
AliasExists bool `json:"exists"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserIDExistsRequest is a request to an application service about whether a
|
||||||
|
// user ID exists
|
||||||
|
type UserIDExistsRequest struct {
|
||||||
|
// UserID we want to lookup
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserIDExistsRequestAccessToken is a request to an application service
|
||||||
|
// about whether a user ID exists. Includes an access token
|
||||||
|
type UserIDExistsRequestAccessToken struct {
|
||||||
|
// UserID we want to lookup
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserIDExistsResponse is a response from an application service about
|
||||||
|
// whether a user ID exists
|
||||||
|
type UserIDExistsResponse struct {
|
||||||
|
UserIDExists bool `json:"exists"`
|
||||||
|
}
|
||||||
|
|
||||||
// AppServiceQueryAPI is used to query user and room alias data from application
|
// AppServiceQueryAPI is used to query user and room alias data from application
|
||||||
// services
|
// services
|
||||||
type AppServiceQueryAPI interface {
|
type AppServiceQueryAPI interface {
|
||||||
|
@ -45,14 +72,22 @@ type AppServiceQueryAPI interface {
|
||||||
RoomAliasExists(
|
RoomAliasExists(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *RoomAliasExistsRequest,
|
req *RoomAliasExistsRequest,
|
||||||
response *RoomAliasExistsResponse,
|
resp *RoomAliasExistsResponse,
|
||||||
|
) error
|
||||||
|
// Check whether a user ID exists within any application service namespaces
|
||||||
|
UserIDExists(
|
||||||
|
ctx context.Context,
|
||||||
|
req *UserIDExistsRequest,
|
||||||
|
resp *UserIDExistsResponse,
|
||||||
) error
|
) error
|
||||||
// TODO: QueryUserIDExists
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppServiceRoomAliasExistsPath is the HTTP path for the RoomAliasExists API
|
// AppServiceRoomAliasExistsPath is the HTTP path for the RoomAliasExists API
|
||||||
const AppServiceRoomAliasExistsPath = "/api/appservice/RoomAliasExists"
|
const AppServiceRoomAliasExistsPath = "/api/appservice/RoomAliasExists"
|
||||||
|
|
||||||
|
// AppServiceUserIDExistsPath is the HTTP path for the UserIDExists API
|
||||||
|
const AppServiceUserIDExistsPath = "/api/appservice/UserIDExists"
|
||||||
|
|
||||||
// httpAppServiceQueryAPI contains the URL to an appservice query API and a
|
// httpAppServiceQueryAPI contains the URL to an appservice query API and a
|
||||||
// reference to a httpClient used to reach it
|
// reference to a httpClient used to reach it
|
||||||
type httpAppServiceQueryAPI struct {
|
type httpAppServiceQueryAPI struct {
|
||||||
|
@ -85,3 +120,59 @@ func (h *httpAppServiceQueryAPI) RoomAliasExists(
|
||||||
apiURL := h.appserviceURL + AppServiceRoomAliasExistsPath
|
apiURL := h.appserviceURL + AppServiceRoomAliasExistsPath
|
||||||
return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserIDExists implements AppServiceQueryAPI
|
||||||
|
func (h *httpAppServiceQueryAPI) UserIDExists(
|
||||||
|
ctx context.Context,
|
||||||
|
request *UserIDExistsRequest,
|
||||||
|
response *UserIDExistsResponse,
|
||||||
|
) error {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "appserviceUserIDExists")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
apiURL := h.appserviceURL + AppServiceUserIDExistsPath
|
||||||
|
return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetreiveUserProfile is a wrapper that queries both the local database and
|
||||||
|
// application services for a given user's profile
|
||||||
|
func RetreiveUserProfile(
|
||||||
|
ctx context.Context,
|
||||||
|
userID string,
|
||||||
|
asAPI AppServiceQueryAPI,
|
||||||
|
accountDB *accounts.Database,
|
||||||
|
) (*authtypes.Profile, error) {
|
||||||
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to query the user from the local database
|
||||||
|
profile, err := accountDB.GetProfileByLocalpart(ctx, localpart)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
return nil, err
|
||||||
|
} else if profile != nil {
|
||||||
|
return profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the appservice component for the existence of an AS user
|
||||||
|
userReq := UserIDExistsRequest{UserID: userID}
|
||||||
|
var userResp UserIDExistsResponse
|
||||||
|
if err = asAPI.UserIDExists(ctx, &userReq, &userResp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no user exists, return
|
||||||
|
if !userResp.UserIDExists {
|
||||||
|
return nil, errors.New("no known profile for given user ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to query the user from the local database again
|
||||||
|
profile, err = accountDB.GetProfileByLocalpart(ctx, localpart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// profile should not be nil at this point
|
||||||
|
return profile, nil
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const roomAliasExistsPath = "/rooms/"
|
const roomAliasExistsPath = "/rooms/"
|
||||||
|
const userIDExistsPath = "/users/"
|
||||||
|
|
||||||
// AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI
|
// AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI
|
||||||
type AppServiceQueryAPI struct {
|
type AppServiceQueryAPI struct {
|
||||||
|
@ -107,6 +108,71 @@ func (a *AppServiceQueryAPI) RoomAliasExists(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserIDExists performs a request to '/users/{userID}' on all known
|
||||||
|
// handling application services until one admits to owning the user ID
|
||||||
|
func (a *AppServiceQueryAPI) UserIDExists(
|
||||||
|
ctx context.Context,
|
||||||
|
request *api.UserIDExistsRequest,
|
||||||
|
response *api.UserIDExistsResponse,
|
||||||
|
) error {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "ApplicationServiceUserID")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
// Create an HTTP client if one does not already exist
|
||||||
|
if a.HTTPClient == nil {
|
||||||
|
a.HTTPClient = makeHTTPClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which application service should handle this request
|
||||||
|
for _, appservice := range a.Cfg.Derived.ApplicationServices {
|
||||||
|
if appservice.URL != "" && appservice.IsInterestedInUserID(request.UserID) {
|
||||||
|
// The full path to the rooms API, includes hs token
|
||||||
|
URL, err := url.Parse(appservice.URL + userIDExistsPath)
|
||||||
|
URL.Path += request.UserID
|
||||||
|
apiURL := URL.String() + "?access_token=" + appservice.HSToken
|
||||||
|
|
||||||
|
// Send a request to each application service. If one responds that it has
|
||||||
|
// created the user, 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.WithFields(log.Fields{
|
||||||
|
"appservice_id": 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.WithFields(log.Fields{
|
||||||
|
"appservice_id": appservice.ID,
|
||||||
|
"status_code": resp.StatusCode,
|
||||||
|
}).Warn("application service responded with non-OK status code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.UserIDExists = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// makeHTTPClient creates an HTTP client with certain options that will be used for all query requests to application services
|
// makeHTTPClient creates an HTTP client with certain options that will be used for all query requests to application services
|
||||||
func makeHTTPClient() *http.Client {
|
func makeHTTPClient() *http.Client {
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
|
@ -131,4 +197,18 @@ func (a *AppServiceQueryAPI) SetupHTTP(servMux *http.ServeMux) {
|
||||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
servMux.Handle(
|
||||||
|
api.AppServiceUserIDExistsPath,
|
||||||
|
common.MakeInternalAPI("appserviceUserIDExists", func(req *http.Request) util.JSONResponse {
|
||||||
|
var request api.UserIDExistsRequest
|
||||||
|
var response api.UserIDExistsResponse
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
if err := a.UserIDExists(req.Context(), &request, &response); err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue