增加备份和导出功能
This commit is contained in:
@ -4,25 +4,32 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
"next-terminal/server/model"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/global/security"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Backup struct {
|
||||
Users []model.User `json:"users"`
|
||||
UserGroups []model.UserGroup `json:"user_groups"`
|
||||
UserGroupMembers []model.UserGroupMember `json:"user_group_members"`
|
||||
Users []model.User `json:"users"`
|
||||
UserGroups []model.UserGroup `json:"user_groups"`
|
||||
|
||||
Strategies []model.Strategy `json:"strategies"`
|
||||
Jobs []model.Job `json:"jobs"`
|
||||
AccessSecurities []model.AccessSecurity `json:"access_securities"`
|
||||
AccessGateways []model.AccessGateway `json:"access_gateways"`
|
||||
Commands []model.Command `json:"commands"`
|
||||
Credentials []model.Credential `json:"credentials"`
|
||||
Assets []model.Asset `json:"assets"`
|
||||
ResourceSharers []model.ResourceSharer `json:"resource_sharers"`
|
||||
Storages []model.Storage `json:"storages"`
|
||||
Strategies []model.Strategy `json:"strategies"`
|
||||
AccessSecurities []model.AccessSecurity `json:"access_securities"`
|
||||
AccessGateways []model.AccessGateway `json:"access_gateways"`
|
||||
Commands []model.Command `json:"commands"`
|
||||
Credentials []model.Credential `json:"credentials"`
|
||||
Assets []map[string]interface{} `json:"assets"`
|
||||
ResourceSharers []model.ResourceSharer `json:"resource_sharers"`
|
||||
Jobs []model.Job `json:"jobs"`
|
||||
}
|
||||
|
||||
func BackupExportEndpoint(c echo.Context) error {
|
||||
@ -37,7 +44,17 @@ func BackupExportEndpoint(c echo.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userGroupMembers, err := userGroupRepository.FindAllUserGroupMembers()
|
||||
if len(userGroups) > 0 {
|
||||
for i := range userGroups {
|
||||
members, err := userGroupRepository.FindMembersById(userGroups[i].ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userGroups[i].Members = members
|
||||
}
|
||||
}
|
||||
|
||||
storages, err := storageRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -66,10 +83,36 @@ func BackupExportEndpoint(c echo.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(credentials) > 0 {
|
||||
for i := range credentials {
|
||||
if err := credentialRepository.Decrypt(&credentials[i], config.GlobalCfg.EncryptionPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
assets, err := assetRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var assetMaps = make([]map[string]interface{}, 0)
|
||||
if len(assets) > 0 {
|
||||
for i := range assets {
|
||||
asset := assets[i]
|
||||
if err := assetRepository.Decrypt(&asset, config.GlobalCfg.EncryptionPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
attributeMap, err := assetRepository.FindAssetAttrMapByAssetId(asset.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemMap := utils.StructToMap(asset)
|
||||
for key := range attributeMap {
|
||||
itemMap[key] = attributeMap[key]
|
||||
}
|
||||
assetMaps = append(assetMaps, itemMap)
|
||||
}
|
||||
}
|
||||
|
||||
resourceSharers, err := resourceSharerRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -78,14 +121,14 @@ func BackupExportEndpoint(c echo.Context) error {
|
||||
backup := Backup{
|
||||
Users: users,
|
||||
UserGroups: userGroups,
|
||||
UserGroupMembers: userGroupMembers,
|
||||
Storages: storages,
|
||||
Strategies: strategies,
|
||||
Jobs: jobs,
|
||||
AccessSecurities: accessSecurities,
|
||||
AccessGateways: accessGateways,
|
||||
Commands: commands,
|
||||
Credentials: credentials,
|
||||
Assets: assets,
|
||||
Assets: assetMaps,
|
||||
ResourceSharers: resourceSharers,
|
||||
}
|
||||
|
||||
@ -98,5 +141,199 @@ func BackupExportEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
func BackupImportEndpoint(c echo.Context) error {
|
||||
return nil
|
||||
var backup Backup
|
||||
if err := c.Bind(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var userIdMapping = make(map[string]string, 0)
|
||||
if len(backup.Users) > 0 {
|
||||
for _, item := range backup.Users {
|
||||
if userRepository.ExistByUsername(item.Username) {
|
||||
continue
|
||||
}
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
item.Password = utils.GenPassword()
|
||||
if err := userRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
userIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
var userGroupIdMapping = make(map[string]string, 0)
|
||||
if len(backup.UserGroups) > 0 {
|
||||
for _, item := range backup.UserGroups {
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
|
||||
var members = make([]string, 0)
|
||||
if len(item.Members) > 0 {
|
||||
for _, member := range item.Members {
|
||||
members = append(members, userIdMapping[member])
|
||||
}
|
||||
}
|
||||
|
||||
if err := userGroupRepository.Create(&item, members); err != nil {
|
||||
return err
|
||||
}
|
||||
userGroupIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.Storages) > 0 {
|
||||
for _, item := range backup.Storages {
|
||||
item.ID = utils.UUID()
|
||||
item.Owner = userIdMapping[item.Owner]
|
||||
if err := storageRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var strategyIdMapping = make(map[string]string, 0)
|
||||
if len(backup.Strategies) > 0 {
|
||||
for _, item := range backup.Strategies {
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
if err := strategyRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
strategyIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.AccessSecurities) > 0 {
|
||||
for _, item := range backup.AccessSecurities {
|
||||
item.ID = utils.UUID()
|
||||
if err := accessSecurityRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新内存中的安全规则
|
||||
rule := &security.Security{
|
||||
ID: item.ID,
|
||||
IP: item.IP,
|
||||
Rule: item.Rule,
|
||||
Priority: item.Priority,
|
||||
}
|
||||
security.GlobalSecurityManager.Add <- rule
|
||||
}
|
||||
}
|
||||
|
||||
var accessGatewayIdMapping = make(map[string]string, 0)
|
||||
if len(backup.AccessGateways) > 0 {
|
||||
for _, item := range backup.AccessGateways {
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
if err := accessGatewayRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
accessGatewayIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.Commands) > 0 {
|
||||
for _, item := range backup.Commands {
|
||||
item.ID = utils.UUID()
|
||||
if err := commandRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var credentialIdMapping = make(map[string]string, 0)
|
||||
if len(backup.Credentials) > 0 {
|
||||
for _, item := range backup.Credentials {
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
if err := credentialRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
credentialIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
var assetIdMapping = make(map[string]string, 0)
|
||||
if len(backup.Assets) > 0 {
|
||||
for _, m := range backup.Assets {
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var item model.Asset
|
||||
if err := json.Unmarshal(data, &item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if item.CredentialId != "" && item.CredentialId != "-" {
|
||||
item.CredentialId = credentialIdMapping[item.CredentialId]
|
||||
}
|
||||
if item.AccessGatewayId != "" && item.AccessGatewayId != "-" {
|
||||
item.AccessGatewayId = accessGatewayIdMapping[item.AccessGatewayId]
|
||||
}
|
||||
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
if err := assetRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := assetRepository.UpdateAttributes(item.ID, item.Protocol, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
active, _ := assetService.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
|
||||
|
||||
if item.Active != active {
|
||||
_ = assetRepository.UpdateActiveById(active, item.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
assetIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.ResourceSharers) > 0 {
|
||||
for _, item := range backup.ResourceSharers {
|
||||
|
||||
userGroupId := userGroupIdMapping[item.UserGroupId]
|
||||
userId := userIdMapping[item.UserId]
|
||||
strategyId := strategyIdMapping[item.StrategyId]
|
||||
resourceId := assetIdMapping[item.ResourceId]
|
||||
|
||||
if err := resourceSharerRepository.AddSharerResources(userGroupId, userId, strategyId, item.ResourceType, []string{resourceId}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.Jobs) > 0 {
|
||||
for _, item := range backup.Jobs {
|
||||
if item.Func == constant.FuncCheckAssetStatusJob {
|
||||
continue
|
||||
}
|
||||
|
||||
resourceIds := strings.Split(item.ResourceIds, ",")
|
||||
if len(resourceIds) > 0 {
|
||||
var newResourceIds = make([]string, 0)
|
||||
for _, resourceId := range resourceIds {
|
||||
newResourceIds = append(newResourceIds, assetIdMapping[resourceId])
|
||||
}
|
||||
item.ResourceIds = strings.Join(newResourceIds, ",")
|
||||
}
|
||||
if err := jobService.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Success(c, "")
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"next-terminal/server/constant"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/global/cache"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/model"
|
||||
@ -20,6 +20,10 @@ func UserCreateEndpoint(c echo.Context) (err error) {
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
if userRepository.ExistByUsername(item.Username) {
|
||||
return Fail(c, -1, "username is already in use")
|
||||
}
|
||||
|
||||
password := item.Password
|
||||
|
||||
var pass []byte
|
||||
@ -71,6 +75,11 @@ func UserPagingEndpoint(c echo.Context) error {
|
||||
func UserUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
account, _ := GetCurrentAccount(c)
|
||||
if account.ID == id {
|
||||
return Fail(c, -1, "cannot modify itself")
|
||||
}
|
||||
|
||||
var item model.User
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
|
@ -8,6 +8,7 @@ type UserGroup struct {
|
||||
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
|
||||
Name string `gorm:"type:varchar(500)" json:"name"`
|
||||
Created utils.JsonTime `json:"created"`
|
||||
Members []string `gorm:"-" json:"members"`
|
||||
}
|
||||
|
||||
type UserGroupForPage struct {
|
||||
|
@ -79,9 +79,7 @@ func (r StorageRepository) FindById(id string) (m model.Storage, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r StorageRepository) FindAll() (o []model.Storage) {
|
||||
if r.DB.Find(&o).Error != nil {
|
||||
return nil
|
||||
}
|
||||
func (r StorageRepository) FindAll() (o []model.Storage, err error) {
|
||||
err = r.DB.Find(&o).Error
|
||||
return
|
||||
}
|
||||
|
@ -90,6 +90,16 @@ func (r UserRepository) FindByUsername(username string) (o model.User, err error
|
||||
return
|
||||
}
|
||||
|
||||
func (r UserRepository) ExistByUsername(username string) (exist bool) {
|
||||
count := int64(0)
|
||||
err := r.DB.Table("users").Where("username = ?", username).Count(&count).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (r UserRepository) FindOnlineUsers() (o []model.User, err error) {
|
||||
err = r.DB.Where("online = ?", true).Find(&o).Error
|
||||
return
|
||||
|
@ -42,7 +42,10 @@ func (r StorageService) InitStorages() error {
|
||||
}
|
||||
|
||||
drivePath := r.GetBaseDrivePath()
|
||||
storages := r.storageRepository.FindAll()
|
||||
storages, err := r.storageRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < len(storages); i++ {
|
||||
storage := storages[i]
|
||||
// 判断是否为遗留的数据:磁盘空间在,但用户已删除
|
||||
@ -137,6 +140,9 @@ func (r StorageService) DeleteStorageById(id string, force bool) error {
|
||||
drivePath := r.GetBaseDrivePath()
|
||||
storage, err := r.storageRepository.FindById(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !force && storage.IsDefault {
|
||||
|
56
server/utils/jsontime.go
Normal file
56
server/utils/jsontime.go
Normal file
@ -0,0 +1,56 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JsonTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func NewJsonTime(t time.Time) JsonTime {
|
||||
return JsonTime{
|
||||
Time: t,
|
||||
}
|
||||
}
|
||||
|
||||
func NowJsonTime() JsonTime {
|
||||
return JsonTime{
|
||||
Time: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (j *JsonTime) MarshalJSON() ([]byte, error) {
|
||||
var stamp = fmt.Sprintf("\"%s\"", j.Format("2006-01-02 15:04:05"))
|
||||
return []byte(stamp), nil
|
||||
}
|
||||
|
||||
func (j *JsonTime) UnmarshalJSON(b []byte) error {
|
||||
s := strings.ReplaceAll(string(b), "\"", "")
|
||||
t, err := time.Parse("2006-01-02 15:04:05", s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*j = NewJsonTime(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j JsonTime) Value() (driver.Value, error) {
|
||||
var zeroTime time.Time
|
||||
if j.Time.UnixNano() == zeroTime.UnixNano() {
|
||||
return nil, nil
|
||||
}
|
||||
return j.Time, nil
|
||||
}
|
||||
|
||||
func (j *JsonTime) Scan(v interface{}) error {
|
||||
value, ok := v.(time.Time)
|
||||
if ok {
|
||||
*j = JsonTime{Time: value}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("can not convert %v to timestamp", v)
|
||||
}
|
44
server/utils/password.go
Normal file
44
server/utils/password.go
Normal file
@ -0,0 +1,44 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type Bcrypt struct {
|
||||
cost int
|
||||
}
|
||||
|
||||
func (b *Bcrypt) Encode(password []byte) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword(password, b.cost)
|
||||
}
|
||||
|
||||
func (b *Bcrypt) Match(hashedPassword, password []byte) error {
|
||||
return bcrypt.CompareHashAndPassword(hashedPassword, password)
|
||||
}
|
||||
|
||||
var Encoder = Bcrypt{
|
||||
cost: bcrypt.DefaultCost,
|
||||
}
|
||||
|
||||
func GenPassword() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
digits := "0123456789"
|
||||
specials := "~=+%^*/()[]{}/!@#$?|"
|
||||
all := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"abcdefghijklmnopqrstuvwxyz" +
|
||||
digits + specials
|
||||
length := 8
|
||||
buf := make([]byte, length)
|
||||
buf[0] = digits[rand.Intn(len(digits))]
|
||||
buf[1] = specials[rand.Intn(len(specials))]
|
||||
for i := 2; i < length; i++ {
|
||||
buf[i] = all[rand.Intn(len(all))]
|
||||
}
|
||||
rand.Shuffle(len(buf), func(i, j int) {
|
||||
buf[i], buf[j] = buf[j], buf[i]
|
||||
})
|
||||
return string(buf)
|
||||
}
|
@ -11,7 +11,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"database/sql/driver"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
@ -36,64 +35,9 @@ import (
|
||||
"github.com/gofrs/uuid"
|
||||
errors2 "github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
type JsonTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func NewJsonTime(t time.Time) JsonTime {
|
||||
return JsonTime{
|
||||
Time: t,
|
||||
}
|
||||
}
|
||||
|
||||
func NowJsonTime() JsonTime {
|
||||
return JsonTime{
|
||||
Time: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (t JsonTime) MarshalJSON() ([]byte, error) {
|
||||
var stamp = fmt.Sprintf("\"%s\"", t.Format("2006-01-02 15:04:05"))
|
||||
return []byte(stamp), nil
|
||||
}
|
||||
|
||||
func (t JsonTime) Value() (driver.Value, error) {
|
||||
var zeroTime time.Time
|
||||
if t.Time.UnixNano() == zeroTime.UnixNano() {
|
||||
return nil, nil
|
||||
}
|
||||
return t.Time, nil
|
||||
}
|
||||
|
||||
func (t *JsonTime) Scan(v interface{}) error {
|
||||
value, ok := v.(time.Time)
|
||||
if ok {
|
||||
*t = JsonTime{Time: value}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("can not convert %v to timestamp", v)
|
||||
}
|
||||
|
||||
type Bcrypt struct {
|
||||
cost int
|
||||
}
|
||||
|
||||
func (b *Bcrypt) Encode(password []byte) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword(password, b.cost)
|
||||
}
|
||||
|
||||
func (b *Bcrypt) Match(hashedPassword, password []byte) error {
|
||||
return bcrypt.CompareHashAndPassword(hashedPassword, password)
|
||||
}
|
||||
|
||||
var Encoder = Bcrypt{
|
||||
cost: bcrypt.DefaultCost,
|
||||
}
|
||||
|
||||
func UUID() string {
|
||||
v4, _ := uuid.NewV4()
|
||||
return v4.String()
|
||||
|
Reference in New Issue
Block a user