Implement profile retrieval over federation - fixes #651

Signed-off-by: Alex Chen <minecnly@gmail.com>
This commit is contained in:
Cnly 2019-07-02 02:04:22 +08:00
parent a0dec456c1
commit b39b26624a
6 changed files with 111 additions and 31 deletions

View file

@ -20,13 +20,13 @@ package api
import (
"context"
"database/sql"
"errors"
"net/http"
"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/common"
commonHTTP "github.com/matrix-org/dendrite/common/http"
opentracing "github.com/opentracing/opentracing-go"
)
@ -164,7 +164,7 @@ func RetreiveUserProfile(
// If no user exists, return
if !userResp.UserIDExists {
return nil, errors.New("no known profile for given user ID")
return nil, common.ErrProfileNoExists
}
// Try to query the user from the local database again

View file

@ -14,7 +14,7 @@
package authtypes
// Profile represents the profile for a Matrix account on this home server.
// Profile represents the profile for a Matrix account.
type Profile struct {
Localpart string
DisplayName string

View file

@ -30,49 +30,75 @@ import (
"github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrix"
"github.com/matrix-org/util"
)
// GetProfile implements GET /profile/{userID}
func GetProfile(
req *http.Request, accountDB *accounts.Database, userID string, asAPI appserviceAPI.AppServiceQueryAPI,
req *http.Request, accountDB *accounts.Database, cfg *config.Dendrite,
userID string,
asAPI appserviceAPI.AppServiceQueryAPI,
federation *gomatrixserverlib.FederationClient,
) util.JSONResponse {
if req.Method != http.MethodGet {
return util.JSONResponse{
Code: http.StatusMethodNotAllowed,
JSON: jsonerror.NotFound("Bad method"),
}
}
profile, err := appserviceAPI.RetreiveUserProfile(req.Context(), userID, asAPI, accountDB)
profile, err := getProfile(req.Context(), accountDB, cfg, userID, asAPI, federation)
if err != nil {
if err == common.ErrProfileNoExists {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
}
} else if x, ok := err.(gomatrix.HTTPError); ok {
if x.Code == http.StatusNotFound {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
}
}
}
return httputil.LogThenError(req, err)
}
res := common.ProfileResponse{
AvatarURL: profile.AvatarURL,
DisplayName: profile.DisplayName,
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: res,
JSON: common.ProfileResponse{
AvatarURL: profile.AvatarURL,
DisplayName: profile.DisplayName,
},
}
}
// GetAvatarURL implements GET /profile/{userID}/avatar_url
func GetAvatarURL(
req *http.Request, accountDB *accounts.Database, userID string, asAPI appserviceAPI.AppServiceQueryAPI,
req *http.Request, accountDB *accounts.Database, cfg *config.Dendrite,
userID string, asAPI appserviceAPI.AppServiceQueryAPI,
federation *gomatrixserverlib.FederationClient,
) util.JSONResponse {
profile, err := appserviceAPI.RetreiveUserProfile(req.Context(), userID, asAPI, accountDB)
profile, err := getProfile(req.Context(), accountDB, cfg, userID, asAPI, federation)
if err != nil {
if err == common.ErrProfileNoExists {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
}
} else if x, ok := err.(gomatrix.HTTPError); ok {
if x.Code == http.StatusNotFound {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
}
}
}
return httputil.LogThenError(req, err)
}
res := common.AvatarURL{
AvatarURL: profile.AvatarURL,
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: res,
JSON: common.AvatarURL{
AvatarURL: profile.AvatarURL,
},
}
}
@ -158,18 +184,34 @@ func SetAvatarURL(
// GetDisplayName implements GET /profile/{userID}/displayname
func GetDisplayName(
req *http.Request, accountDB *accounts.Database, userID string, asAPI appserviceAPI.AppServiceQueryAPI,
req *http.Request, accountDB *accounts.Database, cfg *config.Dendrite,
userID string, asAPI appserviceAPI.AppServiceQueryAPI,
federation *gomatrixserverlib.FederationClient,
) util.JSONResponse {
profile, err := appserviceAPI.RetreiveUserProfile(req.Context(), userID, asAPI, accountDB)
profile, err := getProfile(req.Context(), accountDB, cfg, userID, asAPI, federation)
if err != nil {
if err == common.ErrProfileNoExists {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
}
} else if x, ok := err.(gomatrix.HTTPError); ok {
if x.Code == http.StatusNotFound {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
}
}
}
return httputil.LogThenError(req, err)
}
res := common.DisplayName{
DisplayName: profile.DisplayName,
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: res,
JSON: common.DisplayName{
DisplayName: profile.DisplayName,
},
}
}
@ -253,6 +295,38 @@ func SetDisplayName(
}
}
func getProfile(
ctx context.Context, accountDB *accounts.Database, cfg *config.Dendrite,
userID string,
asAPI appserviceAPI.AppServiceQueryAPI,
federation *gomatrixserverlib.FederationClient,
) (*authtypes.Profile, error) {
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
if err != nil {
return nil, err
}
if domain != cfg.Matrix.ServerName {
profile, fedErr := federation.LookupProfile(ctx, domain, userID, "")
if fedErr != nil {
return nil, fedErr
}
return &authtypes.Profile{
Localpart: localpart,
DisplayName: profile.DisplayName,
AvatarURL: profile.AvatarURL,
}, nil
}
profile, err := appserviceAPI.RetreiveUserProfile(ctx, userID, asAPI, accountDB)
if err != nil {
return nil, err
}
return profile, nil
}
func buildMembershipEvents(
ctx context.Context,
memberships []authtypes.Membership,

View file

@ -240,14 +240,14 @@ func Setup(
r0mux.Handle("/profile/{userID}",
common.MakeExternalAPI("profile", func(req *http.Request) util.JSONResponse {
vars := mux.Vars(req)
return GetProfile(req, accountDB, vars["userID"], asAPI)
return GetProfile(req, accountDB, &cfg, vars["userID"], asAPI, federation)
}),
).Methods(http.MethodGet, http.MethodOptions)
r0mux.Handle("/profile/{userID}/avatar_url",
common.MakeExternalAPI("profile_avatar_url", func(req *http.Request) util.JSONResponse {
vars := mux.Vars(req)
return GetAvatarURL(req, accountDB, vars["userID"], asAPI)
return GetAvatarURL(req, accountDB, &cfg, vars["userID"], asAPI, federation)
}),
).Methods(http.MethodGet, http.MethodOptions)
@ -263,7 +263,7 @@ func Setup(
r0mux.Handle("/profile/{userID}/displayname",
common.MakeExternalAPI("profile_displayname", func(req *http.Request) util.JSONResponse {
vars := mux.Vars(req)
return GetDisplayName(req, accountDB, vars["userID"], asAPI)
return GetDisplayName(req, accountDB, &cfg, vars["userID"], asAPI, federation)
}),
).Methods(http.MethodGet, http.MethodOptions)

View file

@ -15,9 +15,14 @@
package common
import (
"errors"
"strconv"
)
// ErrProfileNoExists is returned when trying to lookup a user's profile that
// doesn't exist locally.
var ErrProfileNoExists = errors.New("no known profile for given user ID")
// AccountData represents account data sent from the client API server to the
// sync API server
type AccountData struct {

View file

@ -142,3 +142,4 @@ Trying to get push rules with unknown rule_id fails with 404
Events come down the correct room
local user can join room with version 5
User can invite local user to room with version 5
Outbound federation can query profile data