Skip to content

Commit

Permalink
Merge pull request #305 from pixlise/feature/impersonate-user
Browse files Browse the repository at this point in the history
Feature/impersonate user
  • Loading branch information
pnemere authored Sep 16, 2024
2 parents 0674568 + 3e973cb commit c811836
Show file tree
Hide file tree
Showing 114 changed files with 1,238 additions and 308 deletions.
7 changes: 7 additions & 0 deletions api/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ type APIConfig struct {

// The GroupId of the group a new user is added to by default as a member
DefaultUserGroupId string

// PIXLISE backup & restore settings
DataBackupBucket string
BackupEnabled bool
RestoreEnabled bool

ImpersonateEnabled bool
}

func homeDir() string {
Expand Down
1 change: 1 addition & 0 deletions api/dbCollections/collections.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const UsersName = "users"
const UserImpersonatorsName = "userImpersonators"
const ViewStatesName = "viewStates"
const WidgetDataName = "widgetData"
const ConnectTempTokensName = "connectTempTokens"

func GetAllCollections() []string {
return []string{
Expand Down
7 changes: 7 additions & 0 deletions api/services/apiServices.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/pixlise/core/v4/core/idgen"
"github.com/pixlise/core/v4/core/jwtparser"
"github.com/pixlise/core/v4/core/logger"
"github.com/pixlise/core/v4/core/mongoDBConnection"
"github.com/pixlise/core/v4/core/timestamper"
"go.mongodb.org/mongo-driver/mongo"
)
Expand Down Expand Up @@ -81,5 +82,11 @@ type APIServices struct {
// Our mongo db connection
MongoDB *mongo.Database

// And how we connected to it (so we can run mongodump later if needed)
MongoDetails mongoDBConnection.MongoConnectionDetails

Notifier INotifier

// The unique identifier of this API instance (so we can log/debug issues that are cross-instance!)
InstanceId string
}
250 changes: 245 additions & 5 deletions api/ws/handlers/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,256 @@ package wsHandler

import (
"errors"
protos "github.com/pixlise/core/v4/generated-protos"
"fmt"
"strings"
"sync"

"github.com/mongodb/mongo-tools/mongodump"
"github.com/pixlise/core/v4/api/services"
"github.com/pixlise/core/v4/api/ws/wsHelpers"
"github.com/pixlise/core/v4/core/mongoDBConnection"
protos "github.com/pixlise/core/v4/generated-protos"
)

func HandleBackupDBReq(req *protos.BackupDBReq, hctx wsHelpers.HandlerContext) (*protos.BackupDBResp, error) {
return nil, errors.New("HandleBackupDBReq not implemented yet")
if len(hctx.Svcs.Config.DataBackupBucket) <= 0 {
err := "PIXLISE Backup bucket not configured!"
hctx.Svcs.Log.Errorf(err)
return nil, errors.New(err)
}

if !hctx.Svcs.Config.BackupEnabled {
err := "PIXLISE Backup not enabled!"
hctx.Svcs.Log.Errorf(err)
return nil, errors.New(err)
}

// Delete any local backups already done so we don't run out of space/have issues with overwriting
err := wsHelpers.ResetLocalMongoBackupDir()
if err != nil {
return nil, err
}

startTimestamp := hctx.Svcs.TimeStamper.GetTimeNowSec()

hctx.Svcs.Log.Infof("PIXLISE Backup Requested, will be written to bucket: %v", hctx.Svcs.Config.DataBackupBucket)

// Run MongoDump, save to a local archive file
dump := wsHelpers.MakeMongoDumpInstance(hctx.Svcs.MongoDetails, mongoDBConnection.GetDatabaseName("pixlise", hctx.Svcs.Config.EnvironmentName))

err = dump.Init()
if err != nil {
hctx.Svcs.Log.Errorf("PIXLISE Backup failed to initialise: %v", err)
return nil, err
}

go runBackup(dump, startTimestamp, hctx.Svcs)

return &protos.BackupDBResp{}, nil
}
func HandleDBAdminConfigGetReq(req *protos.DBAdminConfigGetReq, hctx wsHelpers.HandlerContext) (*protos.DBAdminConfigGetResp, error) {
return nil, errors.New("HandleDBAdminConfigGetReq not implemented yet")

func runBackup(dump *mongodump.MongoDump, startTimestamp int64, svcs *services.APIServices) {
var wg sync.WaitGroup
var errDBDump error
var errScanSync error
var errImageSync error
var errQuantSync error

wg.Add(1)
go func() {
defer wg.Done()

errDBDump = dump.Dump()

if errDBDump == nil {
svcs.Log.Infof("PIXLISE DB Dump complete")
errDBDump = wsHelpers.UploadArchive(svcs)
}
}()

wg.Add(1)
go func() {
defer wg.Done()
svcs.Log.Infof("Syncing scans to bucket")
errScanSync = wsHelpers.SyncScans(svcs)
svcs.Log.Infof("Syncing scans to bucket COMPLETE")
}()

wg.Add(1)
go func() {
defer wg.Done()
svcs.Log.Infof("Syncing quants to bucket")
errQuantSync = wsHelpers.SyncQuants(svcs)
svcs.Log.Infof("Syncing quants to bucket COMPLETE")
}()

wg.Add(1)
go func() {
defer wg.Done()
svcs.Log.Infof("Syncing images to bucket")
errImageSync = wsHelpers.SyncImages(svcs)
svcs.Log.Infof("Syncing images to bucket COMPLETE")
}()

// Wait for all sync tasks
wg.Wait()

var err error
if errDBDump != nil {
err = fmt.Errorf("PIXLISE Backup DB dump failed: %v", errDBDump)
}

if errScanSync != nil {
err = fmt.Errorf("PIXLISE Backup error syncing scans: %v", errScanSync)
}

if errImageSync != nil {
err = fmt.Errorf("PIXLISE Backup error syncing images: %v", errImageSync)
}

if errQuantSync != nil {
err = fmt.Errorf("PIXLISE Backup error syncing quants: %v", errQuantSync)
}

if err != nil {
return
}

endTimestamp := svcs.TimeStamper.GetTimeNowSec()
svcs.Log.Infof("PIXLISE Backup complete in %v sec", endTimestamp-startTimestamp)

// TODO: send an update message to notify anything listening that we're done!
}

func HandleRestoreDBReq(req *protos.RestoreDBReq, hctx wsHelpers.HandlerContext) (*protos.RestoreDBResp, error) {
return nil, errors.New("HandleRestoreDBReq not implemented yet")
// Only allow restore if enabled and we're NOT prod
if !hctx.Svcs.Config.RestoreEnabled {
err := "PIXLISE Restore not enabled!"
hctx.Svcs.Log.Errorf(err)
return nil, errors.New(err)
}

if strings.Contains(strings.ToLower(hctx.Svcs.Config.EnvironmentName), "prod") {
err := "PIXLISE Restore not allowed on environment: " + hctx.Svcs.Config.EnvironmentName
hctx.Svcs.Log.Errorf(err)
return nil, errors.New(err)
}

deleteLocal := true

if deleteLocal {
// Delete any local backups already done so we don't run out of space/have issues with overwriting
err := wsHelpers.ResetLocalMongoBackupDir()
if err != nil {
return nil, err
}
}

startTimestamp := hctx.Svcs.TimeStamper.GetTimeNowSec()

go runRestore(startTimestamp, hctx.Svcs, deleteLocal)

return &protos.RestoreDBResp{}, nil
}

func runRestore(startTimestamp int64, svcs *services.APIServices, downloadRemoteFiles bool) {
var wg sync.WaitGroup
var errDBRestore error
var errScanSync error
var errImageSync error
var errQuantSync error

wg.Add(1)
go func() {
defer wg.Done()

restoreFromDBName := ""
if downloadRemoteFiles {
restoreFromDBName, errDBRestore = wsHelpers.DownloadArchive(svcs)
}

if errDBRestore == nil {
restore, errDBRestore := wsHelpers.MakeMongoRestoreInstance(svcs.MongoDetails, mongoDBConnection.GetDatabaseName("pixlise", svcs.Config.EnvironmentName), restoreFromDBName)

if errDBRestore == nil {
result := restore.Restore()
if result.Err != nil {
errDBRestore = result.Err
} else {
svcs.Log.Infof("Mongo Restore complete: %v successes, %v failures", result.Successes, result.Failures)

if downloadRemoteFiles {
// Delete the local db archive
errDBRestore = wsHelpers.ClearLocalMongoArchive()
}
}
}
}
}()

wg.Add(1)
go func() {
defer wg.Done()
svcs.Log.Infof("Restoring scans to bucket")
errScanSync = wsHelpers.RestoreScans(svcs)
svcs.Log.Infof("Restoring scans to bucket COMPLETE")
}()

wg.Add(1)
go func() {
defer wg.Done()
svcs.Log.Infof("Restoring quants to bucket")
errQuantSync = wsHelpers.RestoreQuants(svcs)
svcs.Log.Infof("Restoring quants to bucket COMPLETE")
}()

wg.Add(1)
go func() {
defer wg.Done()
svcs.Log.Infof("Restoring images to bucket")
errImageSync = wsHelpers.RestoreImages(svcs)
svcs.Log.Infof("Restoring images to bucket COMPLETE")
}()

// Wait for all sync tasks
wg.Wait()

var err error
if errDBRestore != nil {
err = fmt.Errorf("PIXLISE Restore DB restore failed: %v", errDBRestore)
}

if errScanSync != nil {
err = fmt.Errorf("PIXLISE Restore error restoring scans: %v", errScanSync)
}

if errImageSync != nil {
err = fmt.Errorf("PIXLISE Restore error restoring images: %v", errImageSync)
}

if errQuantSync != nil {
err = fmt.Errorf("PIXLISE Restore error restoring quants: %v", errQuantSync)
}

if err != nil {
return
}

endTimestamp := svcs.TimeStamper.GetTimeNowSec()
svcs.Log.Infof("PIXLISE Restore complete in %v sec", endTimestamp-startTimestamp)

// TODO: send an update message to notify anything listening that we're done!
}

func HandleDBAdminConfigGetReq(req *protos.DBAdminConfigGetReq, hctx wsHelpers.HandlerContext) (*protos.DBAdminConfigGetResp, error) {
// Reply depending on how this env is configured
resp := protos.DBAdminConfigGetResp{
CanBackup: len(hctx.Svcs.Config.DataBackupBucket) > 0 && hctx.Svcs.Config.BackupEnabled,
BackupDestination: hctx.Svcs.Config.DataBackupBucket,
CanRestore: len(hctx.Svcs.Config.DataBackupBucket) > 0 && hctx.Svcs.Config.RestoreEnabled,
RestoreFrom: hctx.Svcs.Config.DataBackupBucket,
ImpersonateEnabled: hctx.Svcs.Config.ImpersonateEnabled,
}

return &resp, nil
}
4 changes: 4 additions & 0 deletions api/ws/handlers/user-management.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ func makeUser(from *management.User, db *mongo.Database) *protos.Auth0UserDetail
}

func HandleUserImpersonateReq(req *protos.UserImpersonateReq, hctx wsHelpers.HandlerContext) (*protos.UserImpersonateResp, error) {
if !hctx.Svcs.Config.ImpersonateEnabled {
return nil, fmt.Errorf("Impersonate feature is not enabled on this environment")
}

// NOTE: we set this up in the DB, page refresh will cause it to be applied
coll := hctx.Svcs.MongoDB.Collection(dbCollections.UserImpersonatorsName)
ctx := context.TODO()
Expand Down
Loading

0 comments on commit c811836

Please sign in to comment.