package service import ( "context" "encoding/json" "fmt" "os" "path/filepath" "strings" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/encrypt" "github.com/jinzhu/copier" "go.mongodb.org/mongo-driver/v2/bson" ) type MongodbService struct{} type IMongodbService interface { SearchWithPage(search dto.MongodbDBSearch) (int64, interface{}, error) Create(ctx context.Context, req dto.MongodbDBCreate) (*model.DatabaseMongodb, error) LoadFromRemote(req dto.MongodbLoadDB) error UpdateDescription(req dto.UpdateDescription) error BindUser(req dto.MongodbBind) error ChangePassword(req dto.MongodbPassword) error ChangeRootPassword(req dto.ChangeDBInfo) error LoadPrivileges(req dto.MongodbPrivilegesLoad) (string, error) ChangePrivileges(req dto.MongodbPrivileges) error DeleteCheck(req dto.MongodbDBDeleteCheck) ([]dto.DBResource, error) Delete(ctx context.Context, req dto.MongodbDBDelete) error } func NewIMongodbService() IMongodbService { return &MongodbService{} } func (u *MongodbService) SearchWithPage(search dto.MongodbDBSearch) (int64, interface{}, error) { total, mongodbs, err := mongodbRepo.Page( search.Page, search.PageSize, mongodbRepo.WithByMongodbName(search.Database), repo.WithByLikeName(search.Info), repo.WithOrderRuleBy(search.OrderBy, search.Order), ) var dtoMongodbs []dto.MongodbDBInfo for _, mongodb := range mongodbs { var item dto.MongodbDBInfo if err := copier.Copy(&item, &mongodb); err != nil { return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) } dtoMongodbs = append(dtoMongodbs, item) } return total, dtoMongodbs, err } func (u *MongodbService) UpdateDescription(req dto.UpdateDescription) error { return mongodbRepo.Update(req.ID, map[string]interface{}{"description": req.Description}) } func (u *MongodbService) Create(ctx context.Context, req dto.MongodbDBCreate) (*model.DatabaseMongodb, error) { if cmd.CheckIllegal(req.Name, req.Username, req.Password) { return nil, buserr.New("ErrCmdIllegal") } if !isSupportedMongodbPrivilege(req.Permission) { return nil, buserr.New("ErrCmdIllegal") } mongodb, _ := mongodbRepo.Get(repo.WithByName(req.Name), mongodbRepo.WithByMongodbName(req.Database), repo.WithByFrom(req.From)) if mongodb.ID != 0 { return nil, buserr.New("ErrRecordExist") } var createItem model.DatabaseMongodb if err := copier.Copy(&createItem, &req); err != nil { return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) } createItem.MongodbName = req.Database if err := runMongodbCreate(req.From, req.Database, req.Name, req.Username, req.Password, req.Permission); err != nil { return nil, err } global.LOG.Infof("create mongodb database %s successful", req.Name) if err := mongodbRepo.Create(ctx, &createItem); err != nil { return nil, err } return &createItem, nil } func (u *MongodbService) LoadFromRemote(req dto.MongodbLoadDB) error { databases, err := mongodbRepo.List(mongodbRepo.WithByMongodbName(req.Database)) if err != nil { return err } datas, err := loadMongodbDatabases(req) if err != nil { return err } global.LOG.Infof("sync mongodb databases from %s:%s, found %d items", req.From, req.Database, len(datas)) deleteList := append([]model.DatabaseMongodb(nil), databases...) for _, data := range datas { hasOld := false for i := 0; i < len(databases); i++ { if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].MongodbName, req.Database) { hasOld = true updateMap := map[string]interface{}{"is_delete": false} if len(data.Username) != 0 { updateMap["username"] = data.Username } _ = mongodbRepo.Update(databases[i].ID, updateMap) for j := 0; j < len(deleteList); j++ { if deleteList[j].ID == databases[i].ID { deleteList = append(deleteList[:j], deleteList[j+1:]...) break } } break } } if !hasOld { createItem := model.DatabaseMongodb{ Name: data.Name, From: req.From, MongodbName: req.Database, Username: data.Username, Password: "", Description: "", } if err := mongodbRepo.Create(context.Background(), &createItem); err != nil { return err } } } for _, delItem := range deleteList { _ = mongodbRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true}) } return nil } func (u *MongodbService) BindUser(req dto.MongodbBind) error { if cmd.CheckIllegal(req.Database, req.Name, req.Username, req.Password) { return buserr.New("ErrCmdIllegal") } dbItem, err := mongodbRepo.Get(mongodbRepo.WithByMongodbName(req.Database), repo.WithByName(req.Name)) if err != nil { return err } if err := bindMongodbUser(dbItem.From, dbItem.MongodbName, dbItem.Name, req.Username, req.Password); err != nil { return err } pass, err := encrypt.StringEncrypt(req.Password) if err != nil { return fmt.Errorf("encrypt mongodb db %s password failed, err: %v", req.Name, err) } return mongodbRepo.Update(dbItem.ID, map[string]interface{}{ "username": req.Username, "password": pass, }) } func (u *MongodbService) ChangePassword(req dto.MongodbPassword) error { if cmd.CheckIllegal(req.Database, req.Name, req.Password) { return buserr.New("ErrCmdIllegal") } dbItem, err := mongodbRepo.Get(mongodbRepo.WithByMongodbName(req.Database), repo.WithByName(req.Name)) if err != nil { return err } if dbItem.Username == "" { return buserr.New("ErrRecordNotFound") } if err := updateMongodbPassword(dbItem.From, dbItem.MongodbName, dbItem.Name, dbItem.Username, req.Password); err != nil { return err } pass, err := encrypt.StringEncrypt(req.Password) if err != nil { return fmt.Errorf("encrypt mongodb db %s password failed, err: %v", req.Name, err) } return mongodbRepo.Update(dbItem.ID, map[string]interface{}{"password": pass}) } func (u *MongodbService) ChangeRootPassword(req dto.ChangeDBInfo) error { if cmd.CheckIllegal(req.Value) { return buserr.New("ErrCmdIllegal") } if req.From != constant.AppResourceLocal { return buserr.New("ErrRecordNotFound") } appInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database) if err != nil { return err } if appInfo.UserName == "" { return buserr.New("ErrRecordNotFound") } if err := updateMongodbPassword(req.From, req.Database, "admin", appInfo.UserName, req.Value); err != nil { return err } if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil { return err } remote, err := databaseRepo.Get(repo.WithByName(req.Database)) if err != nil { return err } pass, err := encrypt.StringEncrypt(req.Value) if err != nil { return fmt.Errorf("encrypt mongodb root password failed, err: %v", err) } _ = databaseRepo.Update(remote.ID, map[string]interface{}{"password": pass}) return nil } func (u *MongodbService) DeleteCheck(req dto.MongodbDBDeleteCheck) ([]dto.DBResource, error) { var res []dto.DBResource db, err := mongodbRepo.Get(repo.WithByID(req.ID)) if err != nil { return res, err } if db.From == "local" { app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database) if err != nil { return res, err } apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID)) for _, app := range apps { appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId)) if appInstall.ID != 0 { res = append(res, dto.DBResource{ Type: constant.TypeApp, Name: appInstall.Name, }) } } } else { apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID), appRepo.WithKey(req.Type)) for _, app := range apps { appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId)) if appInstall.ID != 0 { res = append(res, dto.DBResource{ Type: constant.TypeApp, Name: appInstall.Name, }) } } } return res, nil } func (u *MongodbService) LoadPrivileges(req dto.MongodbPrivilegesLoad) (string, error) { dbItem, err := mongodbRepo.Get(mongodbRepo.WithByMongodbName(req.Database), repo.WithByName(req.Name)) if err != nil { return "", err } return loadMongodbPrivilege(dbItem.From, dbItem.MongodbName, dbItem.Name, req.Username) } func (u *MongodbService) ChangePrivileges(req dto.MongodbPrivileges) error { if cmd.CheckIllegal(req.Database, req.Username) { return buserr.New("ErrCmdIllegal") } if !isSupportedMongodbPrivilege(req.Permission) { return buserr.New("ErrCmdIllegal") } dbItem, err := mongodbRepo.Get(mongodbRepo.WithByMongodbName(req.Database), repo.WithByName(req.Name)) if err != nil { return err } return updateMongodbPrivilege(dbItem.From, dbItem.MongodbName, dbItem.Name, req.Username, req.Permission) } func (u *MongodbService) Delete(ctx context.Context, req dto.MongodbDBDelete) error { db, err := mongodbRepo.Get(repo.WithByID(req.ID)) if err != nil && !req.ForceDelete { return err } if err := runMongodbDelete(db.From, req.Database, db.Name); err != nil && !req.ForceDelete { return err } if req.DeleteBackup { uploadDir := filepath.Join(global.Dir.DataDir, fmt.Sprintf("uploads/database/%s/%s/%s", req.Type, req.Database, db.Name)) if _, err := os.Stat(uploadDir); err == nil { _ = os.RemoveAll(uploadDir) } backupDir := filepath.Join(global.Dir.LocalBackupDir, fmt.Sprintf("database/%s/%s/%s", req.Type, req.Database, db.Name)) if _, err := os.Stat(backupDir); err == nil { _ = os.RemoveAll(backupDir) } _ = backupRepo.DeleteRecord(ctx, repo.WithByType(req.Type), repo.WithByName(req.Database), repo.WithByDetailName(db.Name)) global.LOG.Infof("delete mongodb database %s-%s backups successful", req.Database, db.Name) } _ = mongodbRepo.Delete(ctx, repo.WithByID(db.ID)) return nil } func runMongodbCreate(from, database, dbName, username, password, permission string) error { if from == constant.AppResourceRemote { return runRemoteMongodbCreate(database, dbName, username, password, permission) } script, err := buildMongodbCreateScript(dbName, username, password, permission) if err != nil { return err } return runMongodbAdminScript(database, script) } func runMongodbDelete(from, database, dbName string) error { if from == constant.AppResourceRemote { return runRemoteMongodbDelete(database, dbName) } script, err := buildMongodbDeleteScript(dbName) if err != nil { return err } return runMongodbAdminScript(database, script) } func bindMongodbUser(from, connectionName, dbName, username, password string) error { if from == constant.AppResourceRemote { return bindRemoteMongodbUser(connectionName, dbName, username, password) } script, err := buildMongodbBindUserScript(dbName, username, password) if err != nil { return err } return runMongodbAdminScript(connectionName, script) } func updateMongodbPassword(from, connectionName, dbName, username, password string) error { if from == constant.AppResourceRemote { return updateRemoteMongodbPasswordOnly(connectionName, dbName, username, password) } script, err := buildMongodbPasswordScript(dbName, username, password) if err != nil { return err } return runMongodbAdminScript(connectionName, script) } func runMongodbAdminScript(database, script string) error { appInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMongodb, database) if err != nil { return err } if appInfo.ContainerName == "" { return fmt.Errorf("mongodb container not found for database %s", database) } return cmd.NewCommandMgr().Run( "docker", "exec", appInfo.ContainerName, "mongosh", buildMongodbRestoreURI(appInfo.UserName, appInfo.Password), "--quiet", "--eval", script, ) } func runMongodbAdminScriptWithStdout(database, script string) (string, error) { appInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMongodb, database) if err != nil { return "", err } if appInfo.ContainerName == "" { return "", fmt.Errorf("mongodb container not found for database %s", database) } return cmd.NewCommandMgr().RunWithStdout( "docker", "exec", appInfo.ContainerName, "mongosh", buildMongodbRestoreURI(appInfo.UserName, appInfo.Password), "--quiet", "--eval", script, ) } func buildMongodbCreateScript(dbName, username, password, permission string) (string, error) { dbNameJSON, err := json.Marshal(dbName) if err != nil { return "", err } usernameJSON, err := json.Marshal(username) if err != nil { return "", err } passwordJSON, err := json.Marshal(password) if err != nil { return "", err } permissionJSON, err := json.Marshal(permission) if err != nil { return "", err } return strings.TrimSpace(fmt.Sprintf(` const dbName = %s; const userName = %s; const password = %s; const permission = %s; const targetDb = db.getSiblingDB(dbName); targetDb.createCollection("_init"); targetDb.createUser({ user: userName, pwd: password, roles: [{ role: permission, db: dbName }] }); `, dbNameJSON, usernameJSON, passwordJSON, permissionJSON)), nil } func buildMongodbDeleteScript(dbName string) (string, error) { dbNameJSON, err := json.Marshal(dbName) if err != nil { return "", err } return strings.TrimSpace(fmt.Sprintf(` const dbName = %s; const targetDb = db.getSiblingDB(dbName); const dropUsersResult = targetDb.runCommand({ dropAllUsersFromDatabase: 1 }); if (!dropUsersResult || dropUsersResult.ok !== 1) { throw new Error("failed to drop users from " + dbName); } const dropDatabaseResult = targetDb.runCommand({ dropDatabase: 1 }); if (!dropDatabaseResult || dropDatabaseResult.ok !== 1) { throw new Error("failed to drop database " + dbName); } `, dbNameJSON)), nil } func buildMongodbBindUserScript(dbName, username, password string) (string, error) { dbNameJSON, err := json.Marshal(dbName) if err != nil { return "", err } usernameJSON, err := json.Marshal(username) if err != nil { return "", err } passwordJSON, err := json.Marshal(password) if err != nil { return "", err } return strings.TrimSpace(fmt.Sprintf(` const dbName = %s; const userName = %s; const password = %s; const targetDb = db.getSiblingDB(dbName); const userInfo = targetDb.runCommand({ usersInfo: userName, showCredentials: false, showCustomData: false }); if (!userInfo || userInfo.ok !== 1) { throw new Error("failed to load mongodb user " + userName); } const roles = [{ role: "readWrite", db: dbName }]; if (Array.isArray(userInfo.users) && userInfo.users.length > 0) { const result = targetDb.runCommand({ updateUser: userName, pwd: password, roles: roles }); if (!result || result.ok !== 1) { throw new Error("failed to update mongodb user " + userName); } } else { const result = targetDb.runCommand({ createUser: userName, pwd: password, roles: roles }); if (!result || result.ok !== 1) { throw new Error("failed to create mongodb user " + userName); } } `, dbNameJSON, usernameJSON, passwordJSON)), nil } func buildMongodbPasswordScript(dbName, username, password string) (string, error) { dbNameJSON, err := json.Marshal(dbName) if err != nil { return "", err } usernameJSON, err := json.Marshal(username) if err != nil { return "", err } passwordJSON, err := json.Marshal(password) if err != nil { return "", err } return strings.TrimSpace(fmt.Sprintf(` const dbName = %s; const userName = %s; const password = %s; const targetDb = db.getSiblingDB(dbName); const result = targetDb.runCommand({ updateUser: userName, pwd: password }); if (!result || result.ok !== 1) { throw new Error("failed to update mongodb user password " + userName); } `, dbNameJSON, usernameJSON, passwordJSON)), nil } type mongodbSyncItem struct { Name string `json:"name"` Username string `json:"username"` } func loadMongodbDatabases(req dto.MongodbLoadDB) ([]mongodbSyncItem, error) { if req.From == constant.AppResourceRemote { return loadRemoteMongodbDatabases(req.Database) } return loadLocalMongodbDatabases(req.Database) } func loadLocalMongodbDatabases(database string) ([]mongodbSyncItem, error) { script := strings.TrimSpace(` const systemDbs = new Set(["admin", "config", "local"]); const result = db.adminCommand({ listDatabases: 1, nameOnly: false }); if (!result || result.ok !== 1) { throw new Error("failed to list mongodb databases"); } const items = result.databases .filter(item => !systemDbs.has(item.name)) .map(item => ({ name: item.name, username: "" })); print("__1panel_json_begin__"); print(JSON.stringify(items)); print("__1panel_json_end__"); `) stdout, err := runMongodbAdminScriptWithStdout(database, script) if err != nil { return nil, err } items := make([]mongodbSyncItem, 0) jsonResult, err := extractMongodbJSONOutput(stdout) if err != nil { return nil, err } if err := json.Unmarshal(jsonResult, &items); err != nil { return nil, err } return items, nil } func runRemoteMongodbCreate(database, dbName, username, password, permission string) error { info, err := loadRemoteMongodbConnection(database) if err != nil { return err } client, ctx, cancel, err := newRemoteMongodbClient(info) if err != nil { return err } defer cancel() defer client.Disconnect(ctx) targetDB := client.Database(dbName) if err := targetDB.CreateCollection(ctx, "_init"); err != nil { return err } return targetDB.RunCommand(ctx, bson.D{ {Key: "createUser", Value: username}, {Key: "pwd", Value: password}, {Key: "roles", Value: bson.A{ bson.D{{Key: "role", Value: permission}, {Key: "db", Value: dbName}}, }}, }).Err() } func runRemoteMongodbDelete(database, dbName string) error { info, err := loadRemoteMongodbConnection(database) if err != nil { return err } client, ctx, cancel, err := newRemoteMongodbClient(info) if err != nil { return err } defer cancel() defer client.Disconnect(ctx) targetDB := client.Database(dbName) if err := targetDB.RunCommand(ctx, bson.D{{Key: "dropAllUsersFromDatabase", Value: 1}}).Err(); err != nil { return err } return targetDB.RunCommand(ctx, bson.D{{Key: "dropDatabase", Value: 1}}).Err() } func bindRemoteMongodbUser(connectionName, dbName, username, password string) error { info, err := loadRemoteMongodbConnection(connectionName) if err != nil { return err } client, ctx, cancel, err := newRemoteMongodbClient(info) if err != nil { return err } defer cancel() defer client.Disconnect(ctx) targetDB := client.Database(dbName) var userInfo struct { Users []struct{} `bson:"users"` } if err := targetDB.RunCommand(ctx, bson.D{ {Key: "usersInfo", Value: username}, {Key: "showCredentials", Value: false}, {Key: "showCustomData", Value: false}, }).Decode(&userInfo); err != nil { return err } if len(userInfo.Users) > 0 { return targetDB.RunCommand(ctx, bson.D{ {Key: "updateUser", Value: username}, {Key: "pwd", Value: password}, {Key: "roles", Value: bson.A{ bson.D{{Key: "role", Value: "readWrite"}, {Key: "db", Value: dbName}}, }}, }).Err() } return targetDB.RunCommand(ctx, bson.D{ {Key: "createUser", Value: username}, {Key: "pwd", Value: password}, {Key: "roles", Value: bson.A{ bson.D{{Key: "role", Value: "readWrite"}, {Key: "db", Value: dbName}}, }}, }).Err() } func updateRemoteMongodbPasswordOnly(connectionName, dbName, username, password string) error { info, err := loadRemoteMongodbConnection(connectionName) if err != nil { return err } client, ctx, cancel, err := newRemoteMongodbClient(info) if err != nil { return err } defer cancel() defer client.Disconnect(ctx) targetDB := client.Database(dbName) return targetDB.RunCommand(ctx, bson.D{ {Key: "updateUser", Value: username}, {Key: "pwd", Value: password}, }).Err() } func loadRemoteMongodbDatabases(database string) ([]mongodbSyncItem, error) { info, err := loadRemoteMongodbConnection(database) if err != nil { return nil, err } client, ctx, cancel, err := newRemoteMongodbClient(info) if err != nil { return nil, err } defer cancel() defer client.Disconnect(ctx) adminDB := client.Database("admin") var listResult struct { Databases []struct { Name string `bson:"name"` } `bson:"databases"` } if err := adminDB.RunCommand(ctx, bson.D{ {Key: "listDatabases", Value: 1}, {Key: "nameOnly", Value: false}, }).Decode(&listResult); err != nil { return nil, err } systemDbs := map[string]struct{}{ "admin": {}, "config": {}, "local": {}, } items := make([]mongodbSyncItem, 0, len(listResult.Databases)) for _, databaseItem := range listResult.Databases { if _, ok := systemDbs[databaseItem.Name]; ok { continue } items = append(items, mongodbSyncItem{ Name: databaseItem.Name, Username: "", }) } return items, nil } func loadMongodbPrivilege(from, connectionName, dbName, username string) (string, error) { if from == constant.AppResourceRemote { return loadRemoteMongodbPrivilege(connectionName, dbName, username) } return loadLocalMongodbPrivilege(connectionName, dbName, username) } func updateMongodbPrivilege(from, connectionName, dbName, username, permission string) error { if from == constant.AppResourceRemote { return updateRemoteMongodbPrivilege(connectionName, dbName, username, permission) } return updateLocalMongodbPrivilege(connectionName, dbName, username, permission) } func loadLocalMongodbPrivilege(connectionName, dbName, username string) (string, error) { databaseJSON, err := json.Marshal(dbName) if err != nil { return "", err } usernameJSON, err := json.Marshal(username) if err != nil { return "", err } script := strings.TrimSpace(fmt.Sprintf(` const dbName = %s; const userName = %s; const result = db.getSiblingDB(dbName).runCommand({ usersInfo: userName, showCredentials: false, showCustomData: false }); if (!result || result.ok !== 1) { throw new Error("failed to load mongodb user privileges"); } const roles = Array.isArray(result.users) && result.users.length > 0 ? result.users[0].roles || [] : []; const permissions = roles.filter(role => role.db === dbName).map(role => role.role); print("__1panel_json_begin__"); print(JSON.stringify(permissions)); print("__1panel_json_end__"); `, databaseJSON, usernameJSON)) stdout, err := runMongodbAdminScriptWithStdout(connectionName, script) if err != nil { return "", err } var permissions []string jsonResult, err := extractMongodbJSONOutput(stdout) if err != nil { return "", err } if err := json.Unmarshal(jsonResult, &permissions); err != nil { return "", err } return normalizeMongodbPrivilege(permissions), nil } func updateLocalMongodbPrivilege(connectionName, dbName, username, permission string) error { databaseJSON, err := json.Marshal(dbName) if err != nil { return err } usernameJSON, err := json.Marshal(username) if err != nil { return err } permissionJSON, err := json.Marshal(permission) if err != nil { return err } script := strings.TrimSpace(fmt.Sprintf(` const dbName = %s; const userName = %s; const permission = %s; const targetDb = db.getSiblingDB(dbName); const userInfo = targetDb.runCommand({ usersInfo: userName, showCredentials: false, showCustomData: false }); if (!userInfo || userInfo.ok !== 1) { throw new Error("failed to load mongodb user privileges"); } if (!Array.isArray(userInfo.users) || userInfo.users.length === 0) { throw new Error("mongodb user not found: " + userName); } const roles = (userInfo.users[0].roles || []).filter(role => role.db !== dbName); roles.push({ role: permission, db: dbName }); const result = targetDb.runCommand({ updateUser: userName, roles: roles }); if (!result || result.ok !== 1) { throw new Error("failed to update mongodb user privileges"); } `, databaseJSON, usernameJSON, permissionJSON)) return runMongodbAdminScript(connectionName, script) } func loadRemoteMongodbPrivilege(connectionName, dbName, username string) (string, error) { info, err := loadRemoteMongodbConnection(connectionName) if err != nil { return "", err } client, ctx, cancel, err := newRemoteMongodbClient(info) if err != nil { return "", err } defer cancel() defer client.Disconnect(ctx) targetDB := client.Database(dbName) var result struct { Users []struct { Roles []struct { Role string `bson:"role"` DB string `bson:"db"` } `bson:"roles"` } `bson:"users"` } if err := targetDB.RunCommand(ctx, bson.D{ {Key: "usersInfo", Value: username}, {Key: "showCredentials", Value: false}, {Key: "showCustomData", Value: false}, }).Decode(&result); err != nil { return "", err } permissions := make([]string, 0) if len(result.Users) > 0 { for _, role := range result.Users[0].Roles { if role.DB == dbName { permissions = append(permissions, role.Role) } } } return normalizeMongodbPrivilege(permissions), nil } func updateRemoteMongodbPrivilege(connectionName, dbName, username, permission string) error { info, err := loadRemoteMongodbConnection(connectionName) if err != nil { return err } client, ctx, cancel, err := newRemoteMongodbClient(info) if err != nil { return err } defer cancel() defer client.Disconnect(ctx) targetDB := client.Database(dbName) var userInfo struct { Users []struct { Roles []struct { Role string `bson:"role"` DB string `bson:"db"` } `bson:"roles"` } `bson:"users"` } if err := targetDB.RunCommand(ctx, bson.D{ {Key: "usersInfo", Value: username}, {Key: "showCredentials", Value: false}, {Key: "showCustomData", Value: false}, }).Decode(&userInfo); err != nil { return err } if len(userInfo.Users) == 0 { return fmt.Errorf("mongodb user %s not found in database %s", username, dbName) } roles := make(bson.A, 0, len(userInfo.Users[0].Roles)+1) for _, role := range userInfo.Users[0].Roles { if role.DB == dbName { continue } roles = append(roles, bson.D{{Key: "role", Value: role.Role}, {Key: "db", Value: role.DB}}) } roles = append(roles, bson.D{{Key: "role", Value: permission}, {Key: "db", Value: dbName}}) return targetDB.RunCommand(ctx, bson.D{ {Key: "updateUser", Value: username}, {Key: "roles", Value: roles}, }).Err() } func normalizeMongodbPrivilege(permissions []string) string { roleMap := map[string]struct{}{ "dbOwner": {}, "read": {}, "readWrite": {}, "userAdmin": {}, } for _, permission := range permissions { if _, ok := roleMap[permission]; ok { return permission } } return "" } func isSupportedMongodbPrivilege(permission string) bool { switch permission { case "dbOwner", "read", "readWrite", "userAdmin": return true default: return false } } func extractMongodbJSONOutput(stdout string) ([]byte, error) { const ( beginMark = "__1panel_json_begin__" endMark = "__1panel_json_end__" ) output := strings.TrimSpace(stdout) if output == "" { return nil, fmt.Errorf("empty mongodb command output") } beginIndex := strings.Index(output, beginMark) endIndex := strings.LastIndex(output, endMark) if beginIndex >= 0 && endIndex > beginIndex { content := strings.TrimSpace(output[beginIndex+len(beginMark) : endIndex]) if content != "" { return []byte(content), nil } } lines := strings.Split(output, "\n") for i := len(lines) - 1; i >= 0; i-- { line := strings.TrimSpace(lines[i]) if line == "" { continue } return []byte(line), nil } return nil, fmt.Errorf("mongodb command output does not contain json result") }