From b7cfc2e057439334e3ddfa69cebff653f3114d00 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Sun, 5 Nov 2017 10:03:54 -0800 Subject: [PATCH] Federation: Implement Query Profile API (#317) * Federation: Implement Query Profile API Implements the server portion of: `GET /_matrix/federation/v1/query/profile?user_id=...&field=...` Closes #278 Signed-off-by: Andrew (anoa) * Properly export profile-related structs and fix wording Signed-off-by: Andrew (anoa) * Check provided user's domain matches our own. --- .../dendrite/clientapi/jsonerror/jsonerror.go | 12 +++ .../dendrite/clientapi/routing/profile.go | 23 ++--- .../matrix-org/dendrite/common/types.go | 16 ++++ .../dendrite/federationapi/routing/profile.go | 87 +++++++++++++++++++ .../dendrite/federationapi/routing/routing.go | 9 ++ 5 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 src/github.com/matrix-org/dendrite/federationapi/routing/profile.go diff --git a/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go b/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go index 8f168f4f..07fe9030 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go +++ b/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go @@ -67,6 +67,18 @@ func NotFound(msg string) *MatrixError { return &MatrixError{"M_NOT_FOUND", msg} } +// MissingArgument is an error when the client tries to access a resource +// without providing an argument that is required. +func MissingArgument(msg string) *MatrixError { + return &MatrixError{"M_MISSING_ARGUMENT", msg} +} + +// InvalidArgumentValue is an error when the client tries to provide an +// invalid value for a valid argument +func InvalidArgumentValue(msg string) *MatrixError { + return &MatrixError{"M_INVALID_ARGUMENT_VALUE", msg} +} + // MissingToken is an error when the client tries to access a resource which // requires authentication without supplying credentials. func MissingToken(msg string) *MatrixError { diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/profile.go b/src/github.com/matrix-org/dendrite/clientapi/routing/profile.go index da47451f..1403a829 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/profile.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/profile.go @@ -31,19 +31,6 @@ import ( "github.com/matrix-org/util" ) -type profileResponse struct { - AvatarURL string `json:"avatar_url"` - DisplayName string `json:"displayname"` -} - -type avatarURL struct { - AvatarURL string `json:"avatar_url"` -} - -type displayName struct { - DisplayName string `json:"displayname"` -} - // GetProfile implements GET /profile/{userID} func GetProfile( req *http.Request, accountDB *accounts.Database, userID string, @@ -63,7 +50,7 @@ func GetProfile( if err != nil { return httputil.LogThenError(req, err) } - res := profileResponse{ + res := common.ProfileResponse{ AvatarURL: profile.AvatarURL, DisplayName: profile.DisplayName, } @@ -86,7 +73,7 @@ func GetAvatarURL( if err != nil { return httputil.LogThenError(req, err) } - res := avatarURL{ + res := common.AvatarURL{ AvatarURL: profile.AvatarURL, } return util.JSONResponse{ @@ -110,7 +97,7 @@ func SetAvatarURL( changedKey := "avatar_url" - var r avatarURL + var r common.AvatarURL if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil { return *resErr } @@ -178,7 +165,7 @@ func GetDisplayName( if err != nil { return httputil.LogThenError(req, err) } - res := displayName{ + res := common.DisplayName{ DisplayName: profile.DisplayName, } return util.JSONResponse{ @@ -202,7 +189,7 @@ func SetDisplayName( changedKey := "displayname" - var r displayName + var r common.DisplayName if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil { return *resErr } diff --git a/src/github.com/matrix-org/dendrite/common/types.go b/src/github.com/matrix-org/dendrite/common/types.go index 471a2f30..d8c5c5a7 100644 --- a/src/github.com/matrix-org/dendrite/common/types.go +++ b/src/github.com/matrix-org/dendrite/common/types.go @@ -20,3 +20,19 @@ type AccountData struct { RoomID string `json:"room_id"` Type string `json:"type"` } + +// ProfileResponse is a struct containing all known user profile data +type ProfileResponse struct { + AvatarURL string `json:"avatar_url"` + DisplayName string `json:"displayname"` +} + +// AvatarURL is a struct containing only the URL to a user's avatar +type AvatarURL struct { + AvatarURL string `json:"avatar_url"` +} + +// DisplayName is a struct containing only a user's display name +type DisplayName struct { + DisplayName string `json:"displayname"` +} diff --git a/src/github.com/matrix-org/dendrite/federationapi/routing/profile.go b/src/github.com/matrix-org/dendrite/federationapi/routing/profile.go new file mode 100644 index 00000000..48f47366 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/federationapi/routing/profile.go @@ -0,0 +1,87 @@ +// Copyright 2017 New Vector 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 routing + +import ( + "net/http" + + "github.com/matrix-org/dendrite/clientapi/auth/storage/accounts" + "github.com/matrix-org/dendrite/clientapi/httputil" + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" +) + +// GetProfile implements GET /_matrix/federation/v1/query/profile +func GetProfile( + httpReq *http.Request, + accountDB *accounts.Database, + cfg config.Dendrite, +) util.JSONResponse { + userID, field := httpReq.FormValue("user_id"), httpReq.FormValue("field") + + // httpReq.FormValue will return an empty string if value is not found + if userID == "" { + return util.JSONResponse{ + Code: 400, + JSON: jsonerror.MissingArgument("The request body did not contain required argument 'user_id'."), + } + } + + localpart, domain, err := gomatrixserverlib.SplitID('@', userID) + if err != nil { + return httputil.LogThenError(httpReq, err) + } + + if domain != cfg.Matrix.ServerName { + return httputil.LogThenError(httpReq, err) + } + + profile, err := accountDB.GetProfileByLocalpart(httpReq.Context(), localpart) + if err != nil { + return httputil.LogThenError(httpReq, err) + } + + var res interface{} + code := 200 + + if field != "" { + switch field { + case "displayname": + res = common.DisplayName{ + profile.DisplayName, + } + case "avatar_url": + res = common.AvatarURL{ + profile.AvatarURL, + } + default: + code = 400 + res = jsonerror.InvalidArgumentValue("The request body did not contain an allowed value of argument 'field'. Allowed values are either: 'avatar_url', 'displayname'.") + } + } else { + res = common.ProfileResponse{ + profile.AvatarURL, + profile.DisplayName, + } + } + + return util.JSONResponse{ + Code: code, + JSON: res, + } +} 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 c22dd187..c50afd6e 100644 --- a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go @@ -105,6 +105,15 @@ func Setup( }, )).Methods("GET") + v1fedmux.Handle("/query/profile", common.MakeFedAPI( + "federation_query_profile", cfg.Matrix.ServerName, keys, + func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { + return GetProfile( + httpReq, accountDB, cfg, + ) + }, + )).Methods("GET") + v1fedmux.Handle("/version", common.MakeExternalAPI( "federation_version", func(httpReq *http.Request) util.JSONResponse {