mas: implemented PUT /admin/v2/users/{userID} endpoint

MAS requires this endpoint to fetch the data for the account management page
This commit is contained in:
Roman Isaev 2024-12-30 17:14:04 +00:00
parent 9ebcebee43
commit be8d490e56
No known key found for this signature in database
GPG key ID: 7BE2B6A6C89AEC7F
5 changed files with 95 additions and 12 deletions

View file

@ -21,8 +21,10 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
appserviceAPI "github.com/element-hq/dendrite/appservice/api"
clientapi "github.com/element-hq/dendrite/clientapi/api" clientapi "github.com/element-hq/dendrite/clientapi/api"
clienthttputil "github.com/element-hq/dendrite/clientapi/httputil" clienthttputil "github.com/element-hq/dendrite/clientapi/httputil"
"github.com/element-hq/dendrite/clientapi/userutil"
"github.com/element-hq/dendrite/internal/httputil" "github.com/element-hq/dendrite/internal/httputil"
roomserverAPI "github.com/element-hq/dendrite/roomserver/api" roomserverAPI "github.com/element-hq/dendrite/roomserver/api"
"github.com/element-hq/dendrite/setup/config" "github.com/element-hq/dendrite/setup/config"
@ -731,6 +733,76 @@ func AdminCreateOrModifyAccount(req *http.Request, userAPI userapi.ClientUserAPI
} }
} }
func AdminRetrieveAccount(req *http.Request, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI) util.JSONResponse {
logger := util.GetLogger(req.Context())
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error())
}
userID, ok := vars["userID"]
if !ok {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.MissingParam("Expecting user ID."),
}
}
local, domain, err := userutil.ParseUsernameParam(userID, cfg.Matrix)
if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.InvalidParam(err.Error()),
}
}
body := struct {
DisplayName string `json:"display_name"`
AvatarURL string `json:"avatar_url"`
Deactivated bool `json:"deactivated"`
}{}
{
var rs api.QueryAccountByLocalpartResponse
err := userAPI.QueryAccountByLocalpart(req.Context(), &api.QueryAccountByLocalpartRequest{Localpart: local, ServerName: domain}, &rs)
if err == sql.ErrNoRows {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: spec.NotFound(fmt.Sprintf("User '%s' not found", userID)),
}
} else if err != nil {
logger.WithError(err).Error("userAPI.QueryAccountByLocalpart")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.Unknown(err.Error()),
}
}
body.Deactivated = rs.Account.Deactivated
}
{
profile, err := userAPI.QueryProfile(req.Context(), userID)
if err != nil {
if err == appserviceAPI.ErrProfileNotExists {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: spec.NotFound(err.Error()),
}
} else if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.Unknown(err.Error()),
}
}
}
body.AvatarURL = profile.AvatarURL
body.DisplayName = profile.DisplayName
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: body,
}
}
// GetEventReports returns reported events for a given user/room. // GetEventReports returns reported events for a given user/room.
func GetEventReports( func GetEventReports(
req *http.Request, req *http.Request,

View file

@ -344,9 +344,16 @@ func Setup(
})).Methods(http.MethodGet) })).Methods(http.MethodGet)
synapseAdminRouter.Handle("/admin/v2/users/{userID}", synapseAdminRouter.Handle("/admin/v2/users/{userID}",
httputil.MakeServiceAdminAPI("admin_provision_user", m.AdminToken, func(r *http.Request) util.JSONResponse { httputil.MakeServiceAdminAPI("admin_manage_user", m.AdminToken, func(r *http.Request) util.JSONResponse {
switch r.Method {
case http.MethodGet:
return AdminRetrieveAccount(r, cfg, userAPI)
case http.MethodPut:
return AdminCreateOrModifyAccount(r, userAPI) return AdminCreateOrModifyAccount(r, userAPI)
})).Methods(http.MethodPut) default:
return util.JSONResponse{Code: http.StatusMethodNotAllowed, JSON: nil}
}
})).Methods(http.MethodPut, http.MethodGet)
synapseAdminRouter.Handle("/admin/v2/users/{userID}/devices", synapseAdminRouter.Handle("/admin/v2/users/{userID}/devices",
httputil.MakeServiceAdminAPI("admin_user_devices", m.AdminToken, func(r *http.Request) util.JSONResponse { httputil.MakeServiceAdminAPI("admin_user_devices", m.AdminToken, func(r *http.Request) util.JSONResponse {

View file

@ -31,7 +31,6 @@ type UserInternalAPI interface {
FederationUserAPI FederationUserAPI
QuerySearchProfilesAPI // used by p2p demos QuerySearchProfilesAPI // used by p2p demos
QueryAccountByLocalpart(ctx context.Context, req *QueryAccountByLocalpartRequest, res *QueryAccountByLocalpartResponse) (err error)
QueryExternalUserIDByLocalpartAndProvider(ctx context.Context, req *QueryLocalpartExternalIDRequest, res *QueryLocalpartExternalIDResponse) (err error) QueryExternalUserIDByLocalpartAndProvider(ctx context.Context, req *QueryLocalpartExternalIDRequest, res *QueryLocalpartExternalIDResponse) (err error)
PerformLocalpartExternalUserIDCreation(ctx context.Context, req *PerformLocalpartExternalUserIDCreationRequest) (err error) PerformLocalpartExternalUserIDCreation(ctx context.Context, req *PerformLocalpartExternalUserIDCreationRequest) (err error)
} }
@ -89,6 +88,7 @@ type ClientUserAPI interface {
QueryPushers(ctx context.Context, req *QueryPushersRequest, res *QueryPushersResponse) error QueryPushers(ctx context.Context, req *QueryPushersRequest, res *QueryPushersResponse) error
QueryPushRules(ctx context.Context, userID string) (*pushrules.AccountRuleSets, error) QueryPushRules(ctx context.Context, userID string) (*pushrules.AccountRuleSets, error)
QueryAccountAvailability(ctx context.Context, req *QueryAccountAvailabilityRequest, res *QueryAccountAvailabilityResponse) error QueryAccountAvailability(ctx context.Context, req *QueryAccountAvailabilityRequest, res *QueryAccountAvailabilityResponse) error
QueryAccountByLocalpart(ctx context.Context, req *QueryAccountByLocalpartRequest, res *QueryAccountByLocalpartResponse) (err error)
PerformAdminCreateRegistrationToken(ctx context.Context, registrationToken *clientapi.RegistrationToken) (bool, error) PerformAdminCreateRegistrationToken(ctx context.Context, registrationToken *clientapi.RegistrationToken) (bool, error)
PerformAdminListRegistrationTokens(ctx context.Context, returnAll bool, valid bool) ([]clientapi.RegistrationToken, error) PerformAdminListRegistrationTokens(ctx context.Context, returnAll bool, valid bool) ([]clientapi.RegistrationToken, error)
PerformAdminGetRegistrationToken(ctx context.Context, tokenString string) (*clientapi.RegistrationToken, error) PerformAdminGetRegistrationToken(ctx context.Context, tokenString string) (*clientapi.RegistrationToken, error)
@ -461,6 +461,7 @@ type Account struct {
ServerName spec.ServerName ServerName spec.ServerName
AppServiceID string AppServiceID string
AccountType AccountType AccountType AccountType
Deactivated bool
// TODO: Associations (e.g. with application services) // TODO: Associations (e.g. with application services)
} }

View file

@ -55,7 +55,7 @@ const deactivateAccountSQL = "" +
"UPDATE userapi_accounts SET is_deactivated = TRUE WHERE localpart = $1 AND server_name = $2" "UPDATE userapi_accounts SET is_deactivated = TRUE WHERE localpart = $1 AND server_name = $2"
const selectAccountByLocalpartSQL = "" + const selectAccountByLocalpartSQL = "" +
"SELECT localpart, server_name, appservice_id, account_type FROM userapi_accounts WHERE localpart = $1 AND server_name = $2" "SELECT localpart, server_name, appservice_id, account_type, is_deactivated FROM userapi_accounts WHERE localpart = $1 AND server_name = $2"
const selectPasswordHashSQL = "" + const selectPasswordHashSQL = "" +
"SELECT password_hash FROM userapi_accounts WHERE localpart = $1 AND server_name = $2 AND is_deactivated = FALSE" "SELECT password_hash FROM userapi_accounts WHERE localpart = $1 AND server_name = $2 AND is_deactivated = FALSE"
@ -116,6 +116,7 @@ func (s *accountsStatements) InsertAccount(
localpart string, serverName spec.ServerName, localpart string, serverName spec.ServerName,
hash, appserviceID string, accountType api.AccountType, hash, appserviceID string, accountType api.AccountType,
) (*api.Account, error) { ) (*api.Account, error) {
// TODO: can we replace "UnixNano() / 1M" with "UnixMilli()"?
createdTimeMS := time.Now().UnixNano() / 1000000 createdTimeMS := time.Now().UnixNano() / 1000000
stmt := sqlutil.TxStmt(txn, s.insertAccountStmt) stmt := sqlutil.TxStmt(txn, s.insertAccountStmt)
@ -135,6 +136,7 @@ func (s *accountsStatements) InsertAccount(
ServerName: serverName, ServerName: serverName,
AppServiceID: appserviceID, AppServiceID: appserviceID,
AccountType: accountType, AccountType: accountType,
Deactivated: false,
}, nil }, nil
} }
@ -167,7 +169,7 @@ func (s *accountsStatements) SelectAccountByLocalpart(
var acc api.Account var acc api.Account
stmt := s.selectAccountByLocalpartStmt stmt := s.selectAccountByLocalpartStmt
err := stmt.QueryRowContext(ctx, localpart, serverName).Scan(&acc.Localpart, &acc.ServerName, &appserviceIDPtr, &acc.AccountType) err := stmt.QueryRowContext(ctx, localpart, serverName).Scan(&acc.Localpart, &acc.ServerName, &appserviceIDPtr, &acc.AccountType, &acc.Deactivated)
if err != nil { if err != nil {
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
log.WithError(err).Error("Unable to retrieve user from the db") log.WithError(err).Error("Unable to retrieve user from the db")

View file

@ -54,7 +54,7 @@ const deactivateAccountSQL = "" +
"UPDATE userapi_accounts SET is_deactivated = 1 WHERE localpart = $1 AND server_name = $2" "UPDATE userapi_accounts SET is_deactivated = 1 WHERE localpart = $1 AND server_name = $2"
const selectAccountByLocalpartSQL = "" + const selectAccountByLocalpartSQL = "" +
"SELECT localpart, server_name, appservice_id, account_type FROM userapi_accounts WHERE localpart = $1 AND server_name = $2" "SELECT localpart, server_name, appservice_id, account_type, is_deactivated FROM userapi_accounts WHERE localpart = $1 AND server_name = $2"
const selectPasswordHashSQL = "" + const selectPasswordHashSQL = "" +
"SELECT password_hash FROM userapi_accounts WHERE localpart = $1 AND server_name = $2 AND is_deactivated = 0" "SELECT password_hash FROM userapi_accounts WHERE localpart = $1 AND server_name = $2 AND is_deactivated = 0"
@ -135,6 +135,7 @@ func (s *accountsStatements) InsertAccount(
ServerName: serverName, ServerName: serverName,
AppServiceID: appserviceID, AppServiceID: appserviceID,
AccountType: accountType, AccountType: accountType,
Deactivated: false,
}, nil }, nil
} }
@ -167,7 +168,7 @@ func (s *accountsStatements) SelectAccountByLocalpart(
var acc api.Account var acc api.Account
stmt := s.selectAccountByLocalpartStmt stmt := s.selectAccountByLocalpartStmt
err := stmt.QueryRowContext(ctx, localpart, serverName).Scan(&acc.Localpart, &acc.ServerName, &appserviceIDPtr, &acc.AccountType) err := stmt.QueryRowContext(ctx, localpart, serverName).Scan(&acc.Localpart, &acc.ServerName, &appserviceIDPtr, &acc.AccountType, &acc.Deactivated)
if err != nil { if err != nil {
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
log.WithError(err).Error("Unable to retrieve user from the db") log.WithError(err).Error("Unable to retrieve user from the db")