satellite/admin: add endpoint to freeze/unfreeze user
Allow the admin to manually freeze/unfreeze users. github issue: https://github.com/storj/storj/issues/5397 Change-Id: I402ad1bf2e13effb0a5a8ff35bb128d1fcf18448
This commit is contained in:
parent
d23e25ce0f
commit
01932bda42
@ -22,6 +22,7 @@ import (
|
||||
"storj.io/storj/private/version/checker"
|
||||
"storj.io/storj/satellite/admin"
|
||||
"storj.io/storj/satellite/buckets"
|
||||
"storj.io/storj/satellite/console"
|
||||
"storj.io/storj/satellite/console/restkeys"
|
||||
"storj.io/storj/satellite/metabase"
|
||||
"storj.io/storj/satellite/payments"
|
||||
@ -69,6 +70,10 @@ type Admin struct {
|
||||
REST struct {
|
||||
Keys *restkeys.Service
|
||||
}
|
||||
|
||||
FreezeAccounts struct {
|
||||
Service *console.AccountFreezeService
|
||||
}
|
||||
}
|
||||
|
||||
// NewAdmin creates a new satellite admin peer.
|
||||
@ -174,6 +179,7 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB, metabaseDB *m
|
||||
|
||||
peer.Payments.Stripe = stripeClient
|
||||
peer.Payments.Accounts = peer.Payments.Service.Accounts()
|
||||
peer.FreezeAccounts.Service = console.NewAccountFreezeService(db.Console().AccountFreezeEvents(), db.Console().Users(), db.Console().Projects())
|
||||
}
|
||||
|
||||
{ // setup admin endpoint
|
||||
@ -186,7 +192,7 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB, metabaseDB *m
|
||||
adminConfig := config.Admin
|
||||
adminConfig.AuthorizationToken = config.Console.AuthToken
|
||||
|
||||
peer.Admin.Server = admin.NewServer(log.Named("admin"), peer.Admin.Listener, peer.DB, peer.Buckets.Service, peer.REST.Keys, peer.Payments.Accounts, config.Console, adminConfig)
|
||||
peer.Admin.Server = admin.NewServer(log.Named("admin"), peer.Admin.Listener, peer.DB, peer.Buckets.Service, peer.REST.Keys, peer.FreezeAccounts.Service, peer.Payments.Accounts, config.Console, adminConfig)
|
||||
peer.Servers.Add(lifecycle.Item{
|
||||
Name: "admin",
|
||||
Run: peer.Admin.Server.Run,
|
||||
|
@ -61,6 +61,7 @@ type Server struct {
|
||||
payments payments.Accounts
|
||||
buckets *buckets.Service
|
||||
restKeys *restkeys.Service
|
||||
freezeAccounts *console.AccountFreezeService
|
||||
|
||||
nowFn func() time.Time
|
||||
|
||||
@ -69,7 +70,7 @@ type Server struct {
|
||||
}
|
||||
|
||||
// NewServer returns a new administration Server.
|
||||
func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.Service, restKeys *restkeys.Service, accounts payments.Accounts, console consoleweb.Config, config Config) *Server {
|
||||
func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.Service, restKeys *restkeys.Service, freezeAccounts *console.AccountFreezeService, accounts payments.Accounts, console consoleweb.Config, config Config) *Server {
|
||||
server := &Server{
|
||||
log: log,
|
||||
|
||||
@ -79,6 +80,7 @@ func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.S
|
||||
payments: accounts,
|
||||
buckets: buckets,
|
||||
restKeys: restKeys,
|
||||
freezeAccounts: freezeAccounts,
|
||||
|
||||
nowFn: time.Now,
|
||||
|
||||
@ -97,6 +99,8 @@ func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.S
|
||||
api.HandleFunc("/users/{useremail}", server.userInfo).Methods("GET")
|
||||
api.HandleFunc("/users/{useremail}", server.deleteUser).Methods("DELETE")
|
||||
api.HandleFunc("/users/{useremail}/mfa", server.disableUserMFA).Methods("DELETE")
|
||||
api.HandleFunc("/users/{useremail}/freeze", server.freezeUser).Methods("PUT")
|
||||
api.HandleFunc("/users/{useremail}/freeze", server.unfreezeUser).Methods("DELETE")
|
||||
api.HandleFunc("/oauth/clients", server.createOAuthClient).Methods("POST")
|
||||
api.HandleFunc("/oauth/clients/{id}", server.updateOAuthClient).Methods("PUT")
|
||||
api.HandleFunc("/oauth/clients/{id}", server.deleteOAuthClient).Methods("DELETE")
|
||||
|
@ -379,6 +379,22 @@ Blank fields will not be updated.`,
|
||||
func: async (email: string): Promise<null> => {
|
||||
return this.fetch('DELETE', `users/${email}/mfa`) as Promise<null>;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'freeze user',
|
||||
desc: "insert user into account_freeze_events and set user's limits to zero",
|
||||
params: [['email', new InputText('email', true)]],
|
||||
func: async (email: string): Promise<null> => {
|
||||
return this.fetch('PUT', `users/${email}/freeze`) as Promise<null>;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'unfreeze user',
|
||||
desc: "remove user from account_freeze_events and reset user's limits to what is stored in account_freeze_events",
|
||||
params: [['email', new InputText('email', true)]],
|
||||
func: async (email: string): Promise<null> => {
|
||||
return this.fetch('DELETE', `users/${email}/freeze`) as Promise<null>;
|
||||
}
|
||||
}
|
||||
],
|
||||
rest_api_keys: [
|
||||
|
@ -326,6 +326,64 @@ func (server *Server) disableUserMFA(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) freezeUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
userEmail, ok := vars["useremail"]
|
||||
if !ok {
|
||||
sendJSONError(w, "user-email missing", "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
u, err := server.db.Console().Users().GetByEmail(ctx, userEmail)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
sendJSONError(w, fmt.Sprintf("user with email %q does not exist", userEmail),
|
||||
"", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
sendJSONError(w, "failed to get user details",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = server.freezeAccounts.FreezeUser(ctx, u.ID)
|
||||
if err != nil {
|
||||
sendJSONError(w, "failed to freeze user",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) unfreezeUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
userEmail, ok := vars["useremail"]
|
||||
if !ok {
|
||||
sendJSONError(w, "user-email missing", "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
u, err := server.db.Console().Users().GetByEmail(ctx, userEmail)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
sendJSONError(w, fmt.Sprintf("user with email %q does not exist", userEmail),
|
||||
"", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
sendJSONError(w, "failed to get user details",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err = server.freezeAccounts.UnfreezeUser(ctx, u.ID); err != nil {
|
||||
sendJSONError(w, "failed to unfreeze user",
|
||||
err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
|
@ -260,6 +260,66 @@ func TestDisableMFA(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestFreezeUnfreezeUser(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
StorageNodeCount: 0,
|
||||
UplinkCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
|
||||
config.Admin.Address = "127.0.0.1:0"
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
address := planet.Satellites[0].Admin.Admin.Listener.Addr()
|
||||
userPreFreeze, err := planet.Satellites[0].DB.Console().Users().Get(ctx, planet.Uplinks[0].Projects[0].Owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, userPreFreeze.ProjectStorageLimit)
|
||||
require.NotZero(t, userPreFreeze.ProjectBandwidthLimit)
|
||||
|
||||
projectPreFreeze, err := planet.Satellites[0].DB.Console().Projects().Get(ctx, planet.Uplinks[0].Projects[0].ID)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, projectPreFreeze.BandwidthLimit)
|
||||
require.NotZero(t, projectPreFreeze.StorageLimit)
|
||||
|
||||
// freeze can be run multiple times. Test that doing so does not affect Unfreeze result.
|
||||
link := fmt.Sprintf("http://"+address.String()+"/api/users/%s/freeze", userPreFreeze.Email)
|
||||
body := assertReq(ctx, t, link, http.MethodPut, "", http.StatusOK, "", planet.Satellites[0].Config.Console.AuthToken)
|
||||
require.Len(t, body, 0)
|
||||
|
||||
link = fmt.Sprintf("http://"+address.String()+"/api/users/%s/freeze", userPreFreeze.Email)
|
||||
body = assertReq(ctx, t, link, http.MethodPut, "", http.StatusOK, "", planet.Satellites[0].Config.Console.AuthToken)
|
||||
require.Len(t, body, 0)
|
||||
|
||||
userPostFreeze, err := planet.Satellites[0].DB.Console().Users().Get(ctx, userPreFreeze.ID)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, userPostFreeze.ProjectStorageLimit)
|
||||
require.Zero(t, userPostFreeze.ProjectBandwidthLimit)
|
||||
|
||||
projectPostFreeze, err := planet.Satellites[0].DB.Console().Projects().Get(ctx, planet.Uplinks[0].Projects[0].ID)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, projectPostFreeze.BandwidthLimit.Int64())
|
||||
require.Zero(t, projectPostFreeze.StorageLimit.Int64())
|
||||
|
||||
link = fmt.Sprintf("http://"+address.String()+"/api/users/%s/freeze", userPreFreeze.Email)
|
||||
body = assertReq(ctx, t, link, http.MethodDelete, "", http.StatusOK, "", planet.Satellites[0].Config.Console.AuthToken)
|
||||
require.Len(t, body, 0)
|
||||
|
||||
unfrozenUser, err := planet.Satellites[0].DB.Console().Users().Get(ctx, userPreFreeze.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, userPreFreeze.ProjectStorageLimit, unfrozenUser.ProjectStorageLimit)
|
||||
require.Equal(t, userPreFreeze.ProjectBandwidthLimit, unfrozenUser.ProjectBandwidthLimit)
|
||||
|
||||
unfrozenProject, err := planet.Satellites[0].DB.Console().Projects().Get(ctx, projectPreFreeze.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, projectPreFreeze.StorageLimit, unfrozenProject.StorageLimit)
|
||||
require.Equal(t, projectPreFreeze.BandwidthLimit, unfrozenProject.BandwidthLimit)
|
||||
|
||||
body = assertReq(ctx, t, link, http.MethodDelete, "", http.StatusInternalServerError, "", planet.Satellites[0].Config.Console.AuthToken)
|
||||
require.Contains(t, string(body), "not frozen")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserDelete(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
|
Loading…
Reference in New Issue
Block a user