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) <anoa@openmailbox.org>

* Properly export profile-related structs and fix wording

Signed-off-by: Andrew (anoa) <anoa@openmailbox.org>

* Check provided user's domain matches our own.
This commit is contained in:
Andrew Morgan 2017-11-05 10:03:54 -08:00 committed by Erik Johnston
parent 49c040c89f
commit b7cfc2e057
5 changed files with 129 additions and 18 deletions

View file

@ -67,6 +67,18 @@ func NotFound(msg string) *MatrixError {
return &MatrixError{"M_NOT_FOUND", msg} 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 // MissingToken is an error when the client tries to access a resource which
// requires authentication without supplying credentials. // requires authentication without supplying credentials.
func MissingToken(msg string) *MatrixError { func MissingToken(msg string) *MatrixError {

View file

@ -31,19 +31,6 @@ import (
"github.com/matrix-org/util" "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} // GetProfile implements GET /profile/{userID}
func GetProfile( func GetProfile(
req *http.Request, accountDB *accounts.Database, userID string, req *http.Request, accountDB *accounts.Database, userID string,
@ -63,7 +50,7 @@ func GetProfile(
if err != nil { if err != nil {
return httputil.LogThenError(req, err) return httputil.LogThenError(req, err)
} }
res := profileResponse{ res := common.ProfileResponse{
AvatarURL: profile.AvatarURL, AvatarURL: profile.AvatarURL,
DisplayName: profile.DisplayName, DisplayName: profile.DisplayName,
} }
@ -86,7 +73,7 @@ func GetAvatarURL(
if err != nil { if err != nil {
return httputil.LogThenError(req, err) return httputil.LogThenError(req, err)
} }
res := avatarURL{ res := common.AvatarURL{
AvatarURL: profile.AvatarURL, AvatarURL: profile.AvatarURL,
} }
return util.JSONResponse{ return util.JSONResponse{
@ -110,7 +97,7 @@ func SetAvatarURL(
changedKey := "avatar_url" changedKey := "avatar_url"
var r avatarURL var r common.AvatarURL
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil { if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
return *resErr return *resErr
} }
@ -178,7 +165,7 @@ func GetDisplayName(
if err != nil { if err != nil {
return httputil.LogThenError(req, err) return httputil.LogThenError(req, err)
} }
res := displayName{ res := common.DisplayName{
DisplayName: profile.DisplayName, DisplayName: profile.DisplayName,
} }
return util.JSONResponse{ return util.JSONResponse{
@ -202,7 +189,7 @@ func SetDisplayName(
changedKey := "displayname" changedKey := "displayname"
var r displayName var r common.DisplayName
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil { if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
return *resErr return *resErr
} }

View file

@ -20,3 +20,19 @@ type AccountData struct {
RoomID string `json:"room_id"` RoomID string `json:"room_id"`
Type string `json:"type"` 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"`
}

View file

@ -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,
}
}

View file

@ -105,6 +105,15 @@ func Setup(
}, },
)).Methods("GET") )).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( v1fedmux.Handle("/version", common.MakeExternalAPI(
"federation_version", "federation_version",
func(httpReq *http.Request) util.JSONResponse { func(httpReq *http.Request) util.JSONResponse {