提交 v1.3.0 beta

This commit is contained in:
dushixiang
2022-10-23 20:05:13 +08:00
parent 4ff4d37442
commit 112435199a
329 changed files with 18340 additions and 58458 deletions

View File

@ -3,8 +3,9 @@ package service
import (
"context"
"errors"
"next-terminal/server/common/nt"
"next-terminal/server/constant"
"next-terminal/server/common"
"next-terminal/server/dto"
"next-terminal/server/env"
"next-terminal/server/global/cache"
@ -15,6 +16,8 @@ import (
"gorm.io/gorm"
)
var AccessTokenService = new(accessTokenService)
type accessTokenService struct {
baseService
}
@ -22,33 +25,28 @@ type accessTokenService struct {
func (service accessTokenService) GenAccessToken(userId string) error {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
ctx := service.Context(tx)
if err := service.DelAccessToken(ctx, userId); err != nil {
return err
}
user, err := repository.UserRepository.FindById(ctx, userId)
if err != nil {
return err
}
oldAccessToken, err := repository.AccessTokenRepository.FindByUserId(ctx, userId)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
if oldAccessToken.Token != "" {
cache.TokenManager.Delete(oldAccessToken.Token)
}
if err := repository.AccessTokenRepository.DeleteByUserId(ctx, userId); err != nil {
return err
}
token := "forever-" + utils.UUID()
accessToken := &model.AccessToken{
ID: utils.UUID(),
UserId: userId,
Token: token,
Created: utils.NowJsonTime(),
Created: common.NowJsonTime(),
}
authorization := dto.Authorization{
Token: token,
Remember: false,
Type: constant.AccessToken,
Type: nt.AccessToken,
User: &user,
}
@ -71,7 +69,7 @@ func (service accessTokenService) Reload() error {
authorization := dto.Authorization{
Token: accessToken.Token,
Remember: false,
Type: constant.AccessToken,
Type: nt.AccessToken,
User: &user,
}
@ -79,3 +77,14 @@ func (service accessTokenService) Reload() error {
}
return nil
}
func (service accessTokenService) DelAccessToken(ctx context.Context, userId string) error {
oldAccessToken, err := repository.AccessTokenRepository.FindByUserId(ctx, userId)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
if oldAccessToken.Token != "" {
cache.TokenManager.Delete(oldAccessToken.Token)
}
return repository.AccessTokenRepository.DeleteByUserId(ctx, userId)
}

View File

@ -4,9 +4,15 @@ import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"golang.org/x/net/proxy"
"net"
"next-terminal/server/common/nt"
"strconv"
"time"
"next-terminal/server/common"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/env"
"next-terminal/server/model"
"next-terminal/server/repository"
@ -16,6 +22,8 @@ import (
"gorm.io/gorm"
)
var AssetService = new(assetService)
type assetService struct {
baseService
}
@ -116,24 +124,53 @@ func (s assetService) FindByIdAndDecrypt(c context.Context, id string) (model.As
return asset, nil
}
func (s assetService) CheckStatus(accessGatewayId string, ip string, port int) (bool, error) {
if accessGatewayId != "" && accessGatewayId != "-" {
g, err := GatewayService.GetGatewayById(accessGatewayId)
if err != nil {
return false, err
}
uuid := utils.UUID()
defer g.CloseSshTunnel(uuid)
exposedIP, exposedPort, err := g.OpenSshTunnel(uuid, ip, port)
if err != nil {
return false, err
}
return utils.Tcping(exposedIP, exposedPort)
func (s assetService) CheckStatus(asset *model.Asset, ip string, port int) (bool, error) {
attributes, err := repository.AssetRepository.FindAssetAttrMapByAssetId(context.Background(), asset.ID)
if err != nil {
return false, err
}
return utils.Tcping(ip, port)
if "true" == attributes[nt.SocksProxyEnable] {
socks5 := fmt.Sprintf("%s:%s", attributes[nt.SocksProxyHost], attributes[nt.SocksProxyPort])
auth := &proxy.Auth{
User: attributes[nt.SocksProxyUsername],
Password: attributes[nt.SocksProxyPassword],
}
dailer, err := proxy.SOCKS5("tcp", socks5, auth, &net.Dialer{
Timeout: 15 * time.Second,
KeepAlive: 15 * time.Second,
})
if err != nil {
return false, err
}
target := fmt.Sprintf("%s:%s", ip, strconv.Itoa(port))
c, err := dailer.Dial("tcp", target)
if err != nil {
return false, err
}
defer c.Close()
return true, nil
} else {
accessGatewayId := asset.AccessGatewayId
if accessGatewayId != "" && accessGatewayId != "-" {
g, err := GatewayService.GetGatewayById(accessGatewayId)
if err != nil {
return false, err
}
uuid := utils.UUID()
defer g.CloseSshTunnel(uuid)
exposedIP, exposedPort, err := g.OpenSshTunnel(uuid, ip, port)
if err != nil {
return false, err
}
return utils.Tcping(exposedIP, exposedPort)
}
return utils.Tcping(ip, port)
}
}
func (s assetService) Create(ctx context.Context, m echo.Map) (model.Asset, error) {
@ -148,39 +185,22 @@ func (s assetService) Create(ctx context.Context, m echo.Map) (model.Asset, erro
}
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
item.Active = true
if s.InTransaction(ctx) {
return item, s.create(ctx, item, m)
} else {
return item, env.GetDB().Transaction(func(tx *gorm.DB) error {
c := s.Context(tx)
return s.create(c, item, m)
})
}
}
return item, s.Transaction(ctx, func(ctx context.Context) error {
if err := s.Encrypt(&item, config.GlobalCfg.EncryptionPassword); err != nil {
return err
}
if err := repository.AssetRepository.Create(ctx, &item); err != nil {
return err
}
func (s assetService) create(c context.Context, item model.Asset, m echo.Map) error {
if err := s.Encrypt(&item, config.GlobalCfg.EncryptionPassword); err != nil {
return err
}
if err := repository.AssetRepository.Create(c, &item); err != nil {
return err
}
if err := repository.AssetRepository.UpdateAttributes(c, item.ID, item.Protocol, m); err != nil {
return err
}
//go func() {
// active, _ := s.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
//
// if item.Active != active {
// _ = repository.AssetRepository.UpdateActiveById(context.TODO(), active, item.id)
// }
//}()
return nil
if err := repository.AssetRepository.UpdateAttributes(ctx, item.ID, item.Protocol, m); err != nil {
return err
}
return nil
})
}
func (s assetService) DeleteById(id string) error {
@ -195,7 +215,7 @@ func (s assetService) DeleteById(id string) error {
return err
}
// 删除资产与用户的关系
if err := repository.ResourceSharerRepository.DeleteByResourceId(c, id); err != nil {
if err := repository.AuthorisedRepository.DeleteByAssetId(c, id); err != nil {
return err
}
return nil
@ -265,5 +285,5 @@ func (s assetService) UpdateById(id string, m echo.Map) error {
}
func (s assetService) FixSshMode() error {
return repository.AssetRepository.UpdateAttrs(context.TODO(), "ssh-mode", "naive", constant.Native)
return repository.AssetRepository.UpdateAttrs(context.TODO(), "ssh-mode", "naive", nt.Native)
}

View File

@ -0,0 +1,128 @@
package service
import (
"context"
"github.com/pkg/errors"
"gorm.io/gorm"
"next-terminal/server/common"
"next-terminal/server/dto"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
var AuthorisedService = new(authorisedService)
type authorisedService struct {
baseService
}
func (s authorisedService) AuthorisedAssets(ctx context.Context, item *dto.AuthorisedAsset) error {
return s.Transaction(ctx, func(ctx context.Context) error {
var items []model.Authorised
for _, assetId := range item.AssetIds {
id := utils.Sign([]string{assetId, item.UserId, item.UserGroupId})
if err := repository.AuthorisedRepository.DeleteById(ctx, id); err != nil {
return err
}
authorised := model.Authorised{
ID: id,
AssetId: assetId,
CommandFilterId: item.CommandFilterId,
StrategyId: item.StrategyId,
UserId: item.UserId,
UserGroupId: item.UserGroupId,
Created: common.NowJsonTime(),
}
items = append(items, authorised)
}
return repository.AuthorisedRepository.CreateInBatches(ctx, items)
})
}
func (s authorisedService) AuthorisedUsers(ctx context.Context, item *dto.AuthorisedUser) error {
return s.Transaction(ctx, func(ctx context.Context) error {
var items []model.Authorised
for _, userId := range item.UserIds {
id := utils.Sign([]string{item.AssetId, userId, ""})
if err := repository.AuthorisedRepository.DeleteById(ctx, id); err != nil {
return err
}
authorised := model.Authorised{
ID: id,
AssetId: item.AssetId,
CommandFilterId: item.CommandFilterId,
StrategyId: item.StrategyId,
UserId: userId,
UserGroupId: "",
Created: common.NowJsonTime(),
}
items = append(items, authorised)
}
return repository.AuthorisedRepository.CreateInBatches(ctx, items)
})
}
func (s authorisedService) AuthorisedUserGroups(ctx context.Context, item *dto.AuthorisedUserGroup) error {
return s.Transaction(ctx, func(ctx context.Context) error {
var items []model.Authorised
for _, userGroupId := range item.UserGroupIds {
id := utils.Sign([]string{item.AssetId, "", userGroupId})
if err := repository.AuthorisedRepository.DeleteById(ctx, id); err != nil {
return err
}
authorised := model.Authorised{
ID: id,
AssetId: item.AssetId,
CommandFilterId: item.CommandFilterId,
StrategyId: item.StrategyId,
UserId: "",
UserGroupId: userGroupId,
Created: common.NowJsonTime(),
}
items = append(items, authorised)
}
return repository.AuthorisedRepository.CreateInBatches(ctx, items)
})
}
func (s authorisedService) GetAuthorised(userId, assetId string) (item *model.Authorised, err error) {
id := utils.Sign([]string{assetId, userId, ""})
authorised, err := repository.AuthorisedRepository.FindById(context.Background(), id)
if err != nil {
if errors.Is(gorm.ErrRecordNotFound, err) {
groupIds, err := repository.UserGroupMemberRepository.FindUserGroupIdsByUserId(context.Background(), userId)
if err != nil {
return nil, err
}
for _, groupId := range groupIds {
id := utils.Sign([]string{assetId, "", groupId})
authorised, err := repository.AuthorisedRepository.FindById(context.Background(), id)
if err != nil {
continue
}
item = &authorised
break
}
return item, err
}
return nil, err
}
return &authorised, nil
}

View File

@ -6,8 +6,9 @@ import (
"errors"
"strings"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/dto"
"next-terminal/server/env"
"next-terminal/server/global/security"
@ -18,6 +19,8 @@ import (
"gorm.io/gorm"
)
var BackupService = new(backupService)
type backupService struct {
baseService
}
@ -105,11 +108,6 @@ func (service backupService) Export() (error, *dto.Backup) {
}
}
resourceSharers, err := repository.ResourceSharerRepository.FindAll(ctx)
if err != nil {
return err, nil
}
backup := dto.Backup{
Users: users,
UserGroups: userGroups,
@ -121,7 +119,6 @@ func (service backupService) Export() (error, *dto.Backup) {
Commands: commands,
Credentials: credentials,
Assets: assetMaps,
ResourceSharers: resourceSharers,
}
return nil, &backup
}
@ -165,7 +162,7 @@ func (service backupService) Import(backup *dto.Backup) error {
userGroup, err := UserGroupService.Create(ctx, item.Name, members)
if err != nil {
if errors.Is(constant.ErrNameAlreadyUsed, err) {
if errors.Is(nt.ErrNameAlreadyUsed, err) {
// 删除名称重复的用户组
delete(userGroupIdMapping, oldId)
continue
@ -186,7 +183,7 @@ func (service backupService) Import(backup *dto.Backup) error {
}
item.ID = utils.UUID()
item.Owner = owner
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.StorageRepository.Create(ctx, &item); err != nil {
return err
}
@ -199,7 +196,7 @@ func (service backupService) Import(backup *dto.Backup) error {
oldId := item.ID
newId := utils.UUID()
item.ID = newId
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.StrategyRepository.Create(ctx, &item); err != nil {
return err
}
@ -230,7 +227,7 @@ func (service backupService) Import(backup *dto.Backup) error {
oldId := item.ID
newId := utils.UUID()
item.ID = newId
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.GatewayRepository.Create(ctx, &item); err != nil {
return err
}
@ -241,7 +238,7 @@ func (service backupService) Import(backup *dto.Backup) error {
if len(backup.Commands) > 0 {
for _, item := range backup.Commands {
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.CommandRepository.Create(ctx, &item); err != nil {
return err
}
@ -291,23 +288,9 @@ func (service backupService) Import(backup *dto.Backup) error {
}
}
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 := UserService.AddSharerResources(ctx, 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 {
if item.Func == nt.FuncCheckAssetStatusJob {
continue
}

View File

@ -2,8 +2,8 @@ package service
import (
"context"
"next-terminal/server/constant"
"next-terminal/server/common/nt"
"next-terminal/server/env"
"gorm.io/gorm"
)
@ -12,10 +12,20 @@ type baseService struct {
}
func (service baseService) Context(db *gorm.DB) context.Context {
return context.WithValue(context.TODO(), constant.DB, db)
return context.WithValue(context.TODO(), nt.DB, db)
}
func (service baseService) InTransaction(ctx context.Context) bool {
_, ok := ctx.Value(constant.DB).(*gorm.DB)
func (service baseService) inTransaction(ctx context.Context) bool {
_, ok := ctx.Value(nt.DB).(*gorm.DB)
return ok
}
func (service baseService) Transaction(ctx context.Context, f func(ctx context.Context) error) error {
if !service.inTransaction(ctx) {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
ctx := service.Context(tx)
return f(ctx)
})
}
return f(ctx)
}

100
server/service/cli.go Normal file
View File

@ -0,0 +1,100 @@
package service
import (
"context"
"crypto/md5"
"fmt"
"next-terminal/server/common/nt"
"next-terminal/server/env"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"gorm.io/gorm"
)
type Cli struct {
}
func NewCli() *Cli {
return &Cli{}
}
func (cli Cli) ResetPassword(username string) error {
user, err := repository.UserRepository.FindByUsername(context.TODO(), username)
if err != nil {
return err
}
password := "next-terminal"
passwd, err := utils.Encoder.Encode([]byte(password))
if err != nil {
return err
}
u := &model.User{
Password: string(passwd),
ID: user.ID,
}
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
return err
}
return nil
}
func (cli Cli) ResetTotp(username string) error {
user, err := repository.UserRepository.FindByUsername(context.TODO(), username)
if err != nil {
return err
}
u := &model.User{
TOTPSecret: "-",
ID: user.ID,
}
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
return err
}
return nil
}
func (cli Cli) ChangeEncryptionKey(oldEncryptionKey, newEncryptionKey string) error {
oldPassword := []byte(fmt.Sprintf("%x", md5.Sum([]byte(oldEncryptionKey))))
newPassword := []byte(fmt.Sprintf("%x", md5.Sum([]byte(newEncryptionKey))))
return env.GetDB().Transaction(func(tx *gorm.DB) error {
c := context.WithValue(context.TODO(), nt.DB, tx)
credentials, err := repository.CredentialRepository.FindAll(c)
if err != nil {
return err
}
for i := range credentials {
credential := credentials[i]
if err := CredentialService.Decrypt(&credential, oldPassword); err != nil {
return err
}
if err := CredentialService.Encrypt(&credential, newPassword); err != nil {
return err
}
if err := repository.CredentialRepository.UpdateById(c, &credential, credential.ID); err != nil {
return err
}
}
assets, err := repository.AssetRepository.FindAll(c)
if err != nil {
return err
}
for i := range assets {
asset := assets[i]
if err := AssetService.Decrypt(&asset, oldPassword); err != nil {
return err
}
if err := AssetService.Encrypt(&asset, newPassword); err != nil {
return err
}
if err := repository.AssetRepository.UpdateById(c, &asset, asset.ID); err != nil {
return err
}
}
return nil
})
}

View File

@ -4,13 +4,14 @@ import (
"context"
"encoding/base64"
"next-terminal/server/model"
"next-terminal/server/utils"
"next-terminal/server/config"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
var CredentialService = new(credentialService)
type credentialService struct {
}

View File

@ -4,11 +4,12 @@ import (
"context"
"next-terminal/server/global/gateway"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
)
var GatewayService = new(gatewayService)
type gatewayService struct{}
func (r gatewayService) GetGatewayById(accessGatewayId string) (g *gateway.Gateway, err error) {
@ -37,10 +38,8 @@ func (r gatewayService) LoadAll() error {
}
func (r gatewayService) ReLoad(m *model.AccessGateway) *gateway.Gateway {
log.Debugf("重建接入网关「%v」中...", m.Name)
r.DisconnectById(m.ID)
g := gateway.GlobalGatewayManager.Add(m)
log.Debugf("重建接入网关「%v」完成", m.Name)
return g
}

View File

@ -4,7 +4,8 @@ import (
"context"
"errors"
"next-terminal/server/constant"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"next-terminal/server/global/cron"
"next-terminal/server/log"
"next-terminal/server/model"
@ -12,6 +13,8 @@ import (
"next-terminal/server/utils"
)
var JobService = new(jobService)
type jobService struct {
}
@ -20,7 +23,7 @@ func (r jobService) ChangeStatusById(id, status string) error {
if err != nil {
return err
}
if status == constant.JobStatusRunning {
if status == nt.JobStatusRunning {
j, err := getJob(&job)
if err != nil {
return err
@ -29,29 +32,29 @@ func (r jobService) ChangeStatusById(id, status string) error {
if err != nil {
return err
}
log.Debugf("开启计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(cron.GlobalCron.Entries()))
log.Debug("开启计划任务", log.String("任务名称", job.Name), log.Int("运行中计划任务数量", len(cron.GlobalCron.Entries())))
jobForUpdate := model.Job{ID: id, Status: constant.JobStatusRunning, CronJobId: int(entryID)}
jobForUpdate := model.Job{ID: id, Status: nt.JobStatusRunning, CronJobId: int(entryID)}
return repository.JobRepository.UpdateById(context.TODO(), &jobForUpdate)
} else {
cron.GlobalCron.Remove(cron.JobId(job.CronJobId))
log.Debugf("关闭计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(cron.GlobalCron.Entries()))
jobForUpdate := model.Job{ID: id, Status: constant.JobStatusNotRunning}
log.Debug("关闭计划任务", log.String("任务名称", job.Name), log.Int("运行中计划任务数量", len(cron.GlobalCron.Entries())))
jobForUpdate := model.Job{ID: id, Status: nt.JobStatusNotRunning}
return repository.JobRepository.UpdateById(context.TODO(), &jobForUpdate)
}
}
func getJob(j *model.Job) (job cron.Job, err error) {
switch j.Func {
case constant.FuncCheckAssetStatusJob:
case nt.FuncCheckAssetStatusJob:
job = CheckAssetStatusJob{
ID: j.ID,
Mode: j.Mode,
ResourceIds: j.ResourceIds,
Metadata: j.Metadata,
}
case constant.FuncShellJob:
case nt.FuncShellJob:
job = ShellJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata}
default:
return nil, errors.New("未识别的任务")
@ -78,25 +81,23 @@ func (r jobService) InitJob() error {
job := model.Job{
ID: utils.UUID(),
Name: "资产状态检测",
Func: constant.FuncCheckAssetStatusJob,
Func: nt.FuncCheckAssetStatusJob,
Cron: "0 0/10 * * * ?",
Mode: constant.JobModeAll,
Status: constant.JobStatusRunning,
Created: utils.NowJsonTime(),
Updated: utils.NowJsonTime(),
Mode: nt.JobModeAll,
Status: nt.JobStatusRunning,
Created: common.NowJsonTime(),
Updated: common.NowJsonTime(),
}
if err := repository.JobRepository.Create(context.TODO(), &job); err != nil {
return err
}
log.Debugf("创建计划任务「%v」cron「%v」", job.Name, job.Cron)
} else {
for i := range jobs {
if jobs[i].Status == constant.JobStatusRunning {
err := r.ChangeStatusById(jobs[i].ID, constant.JobStatusRunning)
if jobs[i].Status == nt.JobStatusRunning {
err := r.ChangeStatusById(jobs[i].ID, nt.JobStatusRunning)
if err != nil {
return err
}
log.Debugf("启动计划任务「%v」cron「%v」", jobs[i].Name, jobs[i].Cron)
}
}
}
@ -105,7 +106,7 @@ func (r jobService) InitJob() error {
func (r jobService) Create(ctx context.Context, o *model.Job) (err error) {
if o.Status == constant.JobStatusRunning {
if o.Status == nt.JobStatusRunning {
j, err := getJob(o)
if err != nil {
return err
@ -125,8 +126,8 @@ func (r jobService) DeleteJobById(id string) error {
if err != nil {
return err
}
if job.Status == constant.JobStatusRunning {
if err := r.ChangeStatusById(id, constant.JobStatusNotRunning); err != nil {
if job.Status == nt.JobStatusRunning {
if err := r.ChangeStatusById(id, nt.JobStatusNotRunning); err != nil {
return err
}
}
@ -138,10 +139,10 @@ func (r jobService) UpdateById(m *model.Job) error {
return err
}
if err := r.ChangeStatusById(m.ID, constant.JobStatusNotRunning); err != nil {
if err := r.ChangeStatusById(m.ID, nt.JobStatusNotRunning); err != nil {
return err
}
if err := r.ChangeStatusById(m.ID, constant.JobStatusRunning); err != nil {
if err := r.ChangeStatusById(m.ID, nt.JobStatusRunning); err != nil {
return err
}
return nil

View File

@ -3,10 +3,11 @@ package service
import (
"context"
"fmt"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"strings"
"time"
"next-terminal/server/constant"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
@ -26,7 +27,7 @@ func (r CheckAssetStatusJob) Run() {
}
var assets []model.Asset
if r.Mode == constant.JobModeAll {
if r.Mode == nt.JobModeAll {
assets, _ = repository.AssetRepository.FindAll(context.TODO())
} else {
assets, _ = repository.AssetRepository.FindByIds(context.TODO(), strings.Split(r.ResourceIds, ","))
@ -46,7 +47,8 @@ func (r CheckAssetStatusJob) Run() {
ip = asset.IP
port = asset.Port
)
active, err := AssetService.CheckStatus(asset.AccessGatewayId, ip, port)
active, err := AssetService.CheckStatus(&asset, ip, port)
elapsed := time.Since(t1)
if err == nil {
@ -55,8 +57,12 @@ func (r CheckAssetStatusJob) Run() {
msg = fmt.Sprintf("资产「%v」存活状态检测完成存活「%v」耗时「%v」原因 %v", asset.Name, active, elapsed, err.Error())
}
_ = repository.AssetRepository.UpdateActiveById(context.TODO(), active, asset.ID)
log.Infof(msg)
var message = ""
if !active && err != nil {
message = err.Error()
}
_ = repository.AssetRepository.UpdateActiveById(context.TODO(), active, message, asset.ID)
log.Debug(msg)
msgChan <- msg
}()
}
@ -70,7 +76,7 @@ func (r CheckAssetStatusJob) Run() {
jobLog := model.JobLog{
ID: utils.UUID(),
JobId: r.ID,
Timestamp: utils.NowJsonTime(),
Timestamp: common.NowJsonTime(),
Message: message,
}

View File

@ -5,14 +5,15 @@ import (
"encoding/json"
"errors"
"fmt"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"next-terminal/server/common/term"
"strings"
"time"
"next-terminal/server/constant"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/term"
"next-terminal/server/utils"
"gorm.io/gorm"
@ -35,7 +36,7 @@ func (r ShellJob) Run() {
}
var assets []model.Asset
if r.Mode == constant.JobModeAll {
if r.Mode == nt.JobModeAll {
assets, _ = repository.AssetRepository.FindByProtocol(context.TODO(), "ssh")
} else {
assets, _ = repository.AssetRepository.FindByProtocolAndIds(context.TODO(), "ssh", strings.Split(r.ResourceIds, ","))
@ -48,7 +49,7 @@ func (r ShellJob) Run() {
var metadataShell MetadataShell
err := json.Unmarshal([]byte(r.Metadata), &metadataShell)
if err != nil {
log.Errorf("JSON数据解析失败 %v", err)
log.Error("JSON数据解析失败", log.String("err", err.Error()))
return
}
@ -76,7 +77,7 @@ func (r ShellJob) Run() {
return
}
if credential.Type == constant.Custom {
if credential.Type == nt.Custom {
username = credential.Username
password = credential.Password
} else {
@ -97,10 +98,10 @@ func (r ShellJob) Run() {
} else {
msg = fmt.Sprintf("资产「%v」Shell执行失败错误内容为「%v」耗时「%v」", asset.Name, err.Error(), elapsed)
}
log.Infof(msg)
log.Debug(msg)
} else {
msg = fmt.Sprintf("资产「%v」Shell执行成功返回值「%v」耗时「%v」", asset.Name, result, elapsed)
log.Infof(msg)
log.Debug(msg)
}
msgChan <- msg
@ -116,7 +117,7 @@ func (r ShellJob) Run() {
jobLog := model.JobLog{
ID: utils.UUID(),
JobId: r.ID,
Timestamp: utils.NowJsonTime(),
Timestamp: common.NowJsonTime(),
Message: message,
}

View File

@ -0,0 +1,249 @@
package service
import (
"context"
"errors"
"net"
"strings"
"time"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
var LoginPolicyService = new(loginPolicyService)
type loginPolicyService struct {
baseService
}
func (s loginPolicyService) Create(c context.Context, m *model.LoginPolicy) error {
return s.Transaction(c, func(ctx context.Context) error {
if err := repository.LoginPolicyRepository.Create(ctx, m); err != nil {
return err
}
if len(m.TimePeriod) > 0 {
for i := range m.TimePeriod {
m.TimePeriod[i].ID = utils.UUID()
m.TimePeriod[i].LoginPolicyId = m.ID
}
if err := repository.TimePeriodRepository.CreateInBatches(ctx, m.TimePeriod); err != nil {
return err
}
}
return nil
})
}
func (s loginPolicyService) DeleteByIds(ctx context.Context, ids []string) error {
return s.Transaction(ctx, func(ctx context.Context) error {
for _, id := range ids {
if err := repository.LoginPolicyRepository.DeleteById(ctx, id); err != nil {
return err
}
if err := repository.LoginPolicyUserRefRepository.DeleteByLoginPolicyId(ctx, id); err != nil {
return err
}
if err := repository.TimePeriodRepository.DeleteByLoginPolicyId(ctx, id); err != nil {
return err
}
}
return nil
})
}
func (s loginPolicyService) UpdateById(ctx context.Context, m *model.LoginPolicy, id string) error {
return s.Transaction(ctx, func(ctx context.Context) error {
if err := repository.LoginPolicyRepository.UpdateById(ctx, m, id); err != nil {
return err
}
if err := repository.TimePeriodRepository.DeleteByLoginPolicyId(ctx, id); err != nil {
return err
}
if len(m.TimePeriod) > 0 {
for i := range m.TimePeriod {
m.TimePeriod[i].ID = utils.UUID()
m.TimePeriod[i].LoginPolicyId = m.ID
}
if err := repository.TimePeriodRepository.CreateInBatches(ctx, m.TimePeriod); err != nil {
return err
}
}
return nil
})
}
func (s loginPolicyService) FindById(ctx context.Context, id string) (*model.LoginPolicy, error) {
policy, err := repository.LoginPolicyRepository.FindById(ctx, id)
if err != nil {
return nil, err
}
timePeriods, err := repository.TimePeriodRepository.FindByLoginPolicyId(ctx, id)
if err != nil {
return nil, err
}
policy.TimePeriod = timePeriods
return &policy, nil
}
func (s loginPolicyService) Check(userId, clientIp string) error {
ctx := context.Background()
// 按照优先级倒排进行查询
policies, err := repository.LoginPolicyRepository.FindByUserId(ctx, userId)
if err != nil {
return err
}
if len(policies) == 0 {
return nil
}
if err := s.checkClientIp(policies, clientIp); err != nil {
return err
}
if err := s.checkWeekDay(policies); err != nil {
return err
}
return nil
}
func (s loginPolicyService) checkClientIp(policies []model.LoginPolicy, clientIp string) error {
var pass = true
// 优先级低的先进行判断
for _, policy := range policies {
if !policy.Enabled {
continue
}
ipGroups := strings.Split(policy.IpGroup, ",")
for _, group := range ipGroups {
if strings.Contains(group, "/") {
// CIDR
_, ipNet, err := net.ParseCIDR(group)
if err != nil {
continue
}
if !ipNet.Contains(net.ParseIP(clientIp)) {
continue
}
} else if strings.Contains(group, "-") {
// 范围段
split := strings.Split(group, "-")
if len(split) < 2 {
continue
}
start := split[0]
end := split[1]
intReqIP := utils.IpToInt(clientIp)
if intReqIP < utils.IpToInt(start) || intReqIP > utils.IpToInt(end) {
continue
}
} else {
// IP
if group != clientIp {
continue
}
}
pass = policy.Rule == "allow"
}
}
if !pass {
return errors.New("非常抱歉您当前使用的IP地址不允许进行登录。")
}
return nil
}
func (s loginPolicyService) checkWeekDay(policies []model.LoginPolicy) error {
// 获取当前日期是星期几
now := time.Now()
weekday := int(now.Weekday())
hwc := now.Format("15:04")
var timePass = true
// 优先级低的先进行判断
for _, policy := range policies {
if !policy.Enabled {
continue
}
timePeriods, err := repository.TimePeriodRepository.FindByLoginPolicyId(context.Background(), policy.ID)
if err != nil {
return err
}
for _, period := range timePeriods {
if weekday != period.Key {
continue
}
if period.Value == "" {
continue
}
// 只处理对应天的数据
times := strings.Split(period.Value, "、")
for _, t := range times {
timeRange := strings.Split(t, "~")
start := timeRange[0]
end := timeRange[1]
if (start == "00:00" && end == "00:00") || (start <= hwc && hwc <= end) {
timePass = policy.Rule == "allow"
}
}
}
}
if !timePass {
return errors.New("非常抱歉,当前时段不允许您进行登录。")
}
return nil
}
func (s loginPolicyService) Bind(ctx context.Context, loginPolicyId string, items []model.LoginPolicyUserRef) error {
return s.Transaction(ctx, func(ctx context.Context) error {
var results []model.LoginPolicyUserRef
for i := range items {
if items[i].UserId == "" {
continue
}
exist, err := repository.UserRepository.ExistById(ctx, items[i].UserId)
if err != nil {
continue
}
if !exist {
continue
}
refId := utils.Sign([]string{items[i].UserId, loginPolicyId})
if err := repository.LoginPolicyUserRefRepository.DeleteId(ctx, refId); err != nil {
return err
}
results = append(results, model.LoginPolicyUserRef{
ID: refId,
UserId: items[i].UserId,
LoginPolicyId: loginPolicyId,
})
}
if len(results) == 0 {
return nil
}
return repository.LoginPolicyUserRefRepository.CreateInBatches(ctx, results)
})
}
func (s loginPolicyService) Unbind(ctx context.Context, loginPolicyId string, items []model.LoginPolicyUserRef) error {
return s.Transaction(ctx, func(ctx context.Context) error {
for i := range items {
if items[i].UserId == "" {
continue
}
if err := repository.LoginPolicyUserRefRepository.DeleteByLoginPolicyIdAndUserId(ctx, loginPolicyId, items[i].UserId); err != nil {
return err
}
}
return nil
})
}

View File

@ -4,36 +4,39 @@ import (
"context"
"fmt"
"net/smtp"
"next-terminal/server/common/nt"
"next-terminal/server/constant"
"next-terminal/server/branding"
"next-terminal/server/log"
"next-terminal/server/repository"
"github.com/jordan-wright/email"
)
var MailService = new(mailService)
type mailService struct {
}
func (r mailService) SendMail(to, subject, text string) {
propertiesMap := repository.PropertyRepository.FindAllMap(context.TODO())
host := propertiesMap[constant.MailHost]
port := propertiesMap[constant.MailPort]
username := propertiesMap[constant.MailUsername]
password := propertiesMap[constant.MailPassword]
host := propertiesMap[nt.MailHost]
port := propertiesMap[nt.MailPort]
username := propertiesMap[nt.MailUsername]
password := propertiesMap[nt.MailPassword]
if host == "" || port == "" || username == "" || password == "" {
log.Debugf("邮箱信息不完整,跳过发送邮件。")
log.Warn("邮箱信息不完整,跳过发送邮件。")
return
}
e := email.NewEmail()
e.From = fmt.Sprintf("%s <%s>", constant.AppName, username)
e.From = fmt.Sprintf("%s <%s>", branding.Name, username)
e.To = []string{to}
e.Subject = subject
e.Text = []byte(text)
err := e.Send(host+":"+port, smtp.PlainAuth("", username, password, host))
if err != nil {
log.Errorf("邮件发送失败: %v", err.Error())
log.Error("邮件发送失败", log.String("err", err.Error()))
}
}

77
server/service/menu.go Normal file
View File

@ -0,0 +1,77 @@
package service
import (
"github.com/ucarion/urlpath"
"next-terminal/server/dto"
"next-terminal/server/model"
)
var MenuService = &menuService{}
type menuService struct {
menuPermissions map[string][]*urlpath.Path
treeMenus []*dto.TreeMenu
}
func (s *menuService) Init() error {
if s.menuPermissions == nil {
s.menuPermissions = make(map[string][]*urlpath.Path)
}
// 重载权限路径
for _, menu := range DefaultMenu {
var permissions []*urlpath.Path
for _, permission := range menu.Permissions {
path := urlpath.New(permission.Path)
permissions = append(permissions, &path)
}
s.menuPermissions[menu.ID] = permissions
}
// 重载菜单树缓存
for _, menu := range DefaultMenu {
if menu.ParentId == "root" {
p := &dto.TreeMenu{
Title: menu.Name,
Key: menu.ID,
Children: getChildren(DefaultMenu, menu.ID),
}
s.treeMenus = append(s.treeMenus, p)
}
}
return nil
}
func getChildren(menus []*model.Menu, parentId string) []dto.TreeMenu {
var children []dto.TreeMenu
for _, menu := range menus {
if menu.ParentId == parentId {
p := dto.TreeMenu{
Title: menu.Name,
Key: menu.ID,
Children: getChildren(DefaultMenu, menu.ID),
}
children = append(children, p)
}
}
return children
}
func (s *menuService) GetPermissionByMenu(menu string) []*urlpath.Path {
item, ok := s.menuPermissions[menu]
if ok {
return item
}
return nil
}
func (s *menuService) GetTreeMenus() []*dto.TreeMenu {
return s.treeMenus
}
func (s *menuService) GetMenus() (items []string) {
for _, menu := range DefaultMenu {
items = append(items, menu.ID)
}
return items
}

View File

@ -0,0 +1,408 @@
package service
import "next-terminal/server/model"
var DefaultMenu = []*model.Menu{
model.NewMenu("dashboard", "控制面板", "root",
model.NewPermission("GET", "/overview/counter"),
model.NewPermission("GET", "/overview/asset"),
model.NewPermission("GET", "/overview/date-counter"),
),
model.NewMenu("resource", "资源管理", "root"),
model.NewMenu("asset", "资产管理", "resource",
model.NewPermission("GET", "/assets/paging"),
model.NewPermission("GET", "/tags"),
),
model.NewMenu("asset-access", "接入", "asset",
model.NewPermission("POST", "/sessions"),
model.NewPermission("GET", "/sessions/:id/tunnel"),
model.NewPermission("GET", "/sessions/:id/ssh"),
model.NewPermission("GET", "/sessions/:id/stats"),
model.NewPermission("POST", "/sessions/:id/connect"),
model.NewPermission("POST", "/sessions/:id/resize"),
model.NewPermission("POST", "/sessions/:id/ls"),
model.NewPermission("GET", "/sessions/:id/download"),
model.NewPermission("POST", "/sessions/:id/upload"),
model.NewPermission("POST", "/sessions/:id/edit"),
model.NewPermission("POST", "/sessions/:id/mkdir"),
model.NewPermission("POST", "/sessions/:id/rm"),
model.NewPermission("POST", "/sessions/:id/rename"),
),
model.NewMenu("asset-add", "新建", "asset",
model.NewPermission("POST", "/assets"),
),
model.NewMenu("asset-edit", "编辑", "asset",
model.NewPermission("GET", "/assets/:id"),
model.NewPermission("PUT", "/assets/:id"),
),
model.NewMenu("asset-del", "删除", "asset",
model.NewPermission("DELETE", "/assets/:id"),
),
model.NewMenu("asset-copy", "复制", "asset",
model.NewPermission("GET", "/assets/:id"),
model.NewPermission("POST", "/assets"),
),
model.NewMenu("asset-conn-test", "连通性测试", "asset",
model.NewPermission("POST", "/assets/:id/tcping"),
),
model.NewMenu("asset-import", "导入资产", "asset",
model.NewPermission("POST", "/assets/import"),
),
model.NewMenu("asset-change-owner", "更换所有者", "asset",
model.NewPermission("GET", "/users"),
model.NewPermission("POST", "/assets/:id/change-owner"),
),
model.NewMenu("asset-detail", "详情", "asset",
model.NewPermission("GET", "/assets/:id"),
),
model.NewMenu("asset-authorised-user", "资产授权用户", "asset-detail",
model.NewPermission("GET", "/authorised/users/paging"),
model.NewPermission("GET", "/authorised/selected"),
model.NewPermission("GET", "/users"),
model.NewPermission("GET", "/strategies"),
model.NewPermission("GET", "/command-filters"),
model.NewPermission("POST", "/authorised/users"),
),
model.NewMenu("asset-authorised-user-add", "增加授权", "asset-authorised-user",
model.NewPermission("POST", "/authorised/:id/users"),
),
model.NewMenu("asset-authorised-user-del", "移除授权", "asset-authorised-user",
model.NewPermission("DELETE", "/authorised/:id"),
),
model.NewMenu("asset-authorised-user-group", "资产授权用户组", "asset-detail",
model.NewPermission("GET", "/authorised/user-groups/paging"),
model.NewPermission("GET", "/authorised/selected"),
model.NewPermission("GET", "/user-groups"),
model.NewPermission("GET", "/strategies"),
model.NewPermission("GET", "/command-filters"),
model.NewPermission("POST", "/authorised/user-groups"),
),
model.NewMenu("asset-authorised-user-group-add", "增加授权", "asset-authorised-user-group",
model.NewPermission("POST", "/authorised/:id/user-groups"),
),
model.NewMenu("asset-authorised-user-group-del", "移除授权", "asset-authorised-user-group",
model.NewPermission("DELETE", "/authorised/:id"),
),
model.NewMenu("credential", "授权凭证", "resource",
model.NewPermission("GET", "/credentials/paging"),
),
model.NewMenu("credential-add", "增加", "credential",
model.NewPermission("POST", "/credentials"),
),
model.NewMenu("credential-del", "删除", "credential",
model.NewPermission("DELETE", "/credentials/:id"),
),
model.NewMenu("credential-edit", "修改", "credential",
model.NewPermission("POST", "/credentials/:id"),
),
model.NewMenu("command", "动态指令", "resource",
model.NewPermission("GET", "/commands/paging"),
),
model.NewMenu("command-add", "增加", "command",
model.NewPermission("POST", "/commands"),
),
model.NewMenu("command-edit", "修改", "command",
model.NewPermission("PUT", "/commands/:id"),
),
model.NewMenu("command-del", "删除", "command",
model.NewPermission("DELETE", "/commands/:id"),
),
model.NewMenu("command-exec", "执行", "command",
model.NewPermission("GET", "/assets/paging"),
model.NewPermission("GET", "/tags"),
model.NewPermission("POST", "/sessions"),
model.NewPermission("GET", "/term"),
),
model.NewMenu("command-change-owner", "更换所有者", "command",
model.NewPermission("GET", "/users"),
model.NewPermission("POST", "/commands/:id/change-owner"),
),
model.NewMenu("access-gateway", "接入网关", "resource",
model.NewPermission("GET", "/access-gateways/paging"),
),
model.NewMenu("access-gateway-add", "增加", "access-gateway",
model.NewPermission("POST", "/access-gateways"),
),
model.NewMenu("access-gateway-del", "删除", "access-gateway",
model.NewPermission("DELETE", "/access-gateways/:id"),
),
model.NewMenu("access-gateway-edit", "修改", "access-gateway",
model.NewPermission("PUT", "/access-gateways/:id"),
),
model.NewMenu("session-audit", "会话审计", "root"),
model.NewMenu("online-session", "在线会话", "session-audit",
model.NewPermission("GET", "/sessions/paging"),
),
model.NewMenu("online-session-disconnect", "断开", "online-session",
model.NewPermission("GET", "/sessions/:id/disconnect"),
),
model.NewMenu("online-session-monitor", "监控", "online-session",
model.NewPermission("GET", "/sessions/:id/tunnel-monitor"),
model.NewPermission("GET", "/sessions/:id/ssh-monitor"),
),
model.NewMenu("offline-session", "历史会话", "session-audit",
model.NewPermission("GET", "/sessions/paging"),
),
model.NewMenu("offline-session-playback", "回放", "offline-session",
model.NewPermission("GET", "/sessions/:id/recording"),
),
model.NewMenu("offline-session-del", "删除", "offline-session",
model.NewPermission("DELETE", "/sessions/:id"),
),
model.NewMenu("offline-session-clear", "清空", "offline-session",
model.NewPermission("POST", "/sessions/clear"),
),
model.NewMenu("offline-session-command", "命令记录", "offline-session",
model.NewPermission("GET", "/sessions/:id/commands/paging"),
),
model.NewMenu("offline-session-reviewed", "标记已读", "offline-session"), // TODO
model.NewMenu("offline-session-unreviewed", "标记未读", "offline-session"), // TODO
model.NewMenu("offline-session-reviewed-all", "全部标记已读", "offline-session"), // TODO
model.NewMenu("log-audit", "日志审计", "root"),
model.NewMenu("login-log", "登录日志", "log-audit",
model.NewPermission("GET", "/login-logs/paging"),
),
model.NewMenu("login-log-del", "删除", "login-log",
model.NewPermission("DELETE", "/login-logs/:id"),
),
model.NewMenu("login-log-clear", "清空", "login-log",
model.NewPermission("POST", "/login-logs/clear"),
),
model.NewMenu("storage-log", "文件日志", "log-audit",
model.NewPermission("GET", "/storage-logs/paging"),
),
model.NewMenu("storage-log-del", "删除", "storage-log",
model.NewPermission("DELETE", "/storage-logs/:id"),
),
model.NewMenu("storage-log-clear", "清空", "storage-log",
model.NewPermission("POST", "/storage-logs/clear"),
),
model.NewMenu("ops", "系统运维", "root"),
model.NewMenu("job", "计划任务", "ops",
model.NewPermission("GET", "/jobs/paging"),
),
model.NewMenu("job-add", "增加", "job",
model.NewPermission("POST", "/jobs"),
model.NewPermission("GET", "/assets/paging"),
),
model.NewMenu("job-del", "删除", "job",
model.NewPermission("DELETE", "/jobs/:id"),
),
model.NewMenu("job-edit", "修改", "job",
model.NewPermission("PUT", "/jobs/:id"),
model.NewPermission("GET", "/assets/paging"),
),
model.NewMenu("job-run", "执行", "job",
model.NewPermission("POST", "/jobs/:id/exec"),
),
model.NewMenu("job-change-status", "开启/关闭", "job"),
model.NewMenu("job-log", "日志", "job",
model.NewPermission("GET", "/jobs/:id/logs/paging"),
),
model.NewMenu("storage", "磁盘空间", "ops",
model.NewPermission("GET", "/storages/paging"),
),
model.NewMenu("storage-add", "增加", "storage",
model.NewPermission("POST", "/storages"),
),
model.NewMenu("storage-del", "删除", "storage",
model.NewPermission("DELETE", "/storages/:id"),
),
model.NewMenu("storage-edit", "修改", "storage",
model.NewPermission("PUT", "/storages/:id"),
),
model.NewMenu("storage-browse", "浏览", "storage",
model.NewPermission("GET", "/storages/:id/ls"),
),
model.NewMenu("storage-browse-download", "下载", "storage-browse",
model.NewPermission("GET", "/storages/:id/download"),
),
model.NewMenu("storage-browse-upload", "上传", "storage-browse",
model.NewPermission("POST", "/storages/:id/upload"),
),
model.NewMenu("storage-browse-mkdir", "创建文件夹", "storage-browse",
model.NewPermission("POST", "/storages/:id/mkdir"),
),
model.NewMenu("storage-browse-rm", "删除", "storage-browse",
model.NewPermission("POST", "/storages/:id/rm"),
),
model.NewMenu("storage-browse-rename", "重命名", "storage-browse",
model.NewPermission("POST", "/storages/:id/rename"),
),
model.NewMenu("storage-browse-edit", "编辑", "storage-browse",
model.NewPermission("POST", "/storages/:id/edit"),
),
model.NewMenu("monitoring", "系统监控", "ops",
model.NewPermission("GET", "/overview/ps"),
),
model.NewMenu("security", "安全策略", "root"),
model.NewMenu("access-security", "访问安全", "security",
model.NewPermission("GET", "/securities/paging"),
),
model.NewMenu("access-security-add", "增加", "access-security",
model.NewPermission("POST", "/securities"),
),
model.NewMenu("access-security-del", "删除", "access-security",
model.NewPermission("DELETE", "/securities/:id"),
),
model.NewMenu("access-security-edit", "修改", "access-security",
model.NewPermission("PUT", "/securities/:id"),
),
model.NewMenu("login-policy", "登录策略", "security",
model.NewPermission("GET", "/login-policies/paging"),
),
model.NewMenu("login-policy-add", "增加", "login-policy",
model.NewPermission("POST", "/login-policies"),
),
model.NewMenu("login-policy-del", "删除", "login-policy",
model.NewPermission("DELETE", "/login-policies/:id"),
),
model.NewMenu("login-policy-edit", "修改", "login-policy",
model.NewPermission("PUT", "/login-policies/:id"),
),
model.NewMenu("login-policy-detail", "详情", "login-policy",
model.NewPermission("GET", "/login-policies/:id"),
),
model.NewMenu("login-policy-bind-user", "绑定用户", "login-policy-detail",
model.NewPermission("GET", "/login-policies/:id/users/paging"),
),
model.NewMenu("login-policy-unbind-user", "解绑", "user-login-policy",
model.NewPermission("DELETE", "/authorised/:id"),
),
model.NewMenu("identity", "用户管理", "root"),
model.NewMenu("user", "用户管理", "identity",
model.NewPermission("GET", "/users/paging"),
model.NewPermission("GET", "/roles"),
),
model.NewMenu("user-add", "增加", "user",
model.NewPermission("POST", "/users"),
),
model.NewMenu("user-del", "删除", "user",
model.NewPermission("DELETE", "/users/:id"),
),
model.NewMenu("user-edit", "修改", "user",
model.NewPermission("GET", "/users/:id"),
model.NewPermission("PUT", "/users/:id"),
),
model.NewMenu("user-change-password", "修改密码", "user",
model.NewPermission("POST", "/users/:id/change-password"),
),
model.NewMenu("user-reset-totp", "重置双因素认证", "user",
model.NewPermission("POST", "/users/:id/reset-totp"),
),
model.NewMenu("user-detail", "用户详情", "user",
model.NewPermission("GET", "/users/:id"),
model.NewPermission("GET", "/authorised/assets/paging"),
),
model.NewMenu("user-authorised-asset", "授权的资产", "user-detail",
model.NewPermission("GET", "/authorised/assets/paging"),
),
model.NewMenu("user-bind-asset", "授权", "user-authorised-asset",
model.NewPermission("GET", "/authorised/selected"),
model.NewPermission("GET", "/assets"),
model.NewPermission("GET", "/strategies"),
model.NewPermission("GET", "/command-filters"),
),
model.NewMenu("user-unbind-asset", "移除", "user-authorised-asset",
model.NewPermission("DELETE", "/authorised/:id"),
),
model.NewMenu("user-login-policy", "登录策略", "user-detail",
model.NewPermission("GET", "/login-policies/paging", "userId"),
),
model.NewMenu("user-unbind-login-policy", "解绑", "user-login-policy",
model.NewPermission("DELETE", "/authorised/:id"),
),
model.NewMenu("role", "角色管理", "identity",
model.NewPermission("GET", "/roles/paging"),
),
model.NewMenu("role-add", "增加", "role",
model.NewPermission("POST", "/roles"),
),
model.NewMenu("role-del", "删除", "role",
model.NewPermission("DELETE", "/roles/:id"),
),
model.NewMenu("role-edit", "修改", "role",
model.NewPermission("GET", "/roles/:id"),
model.NewPermission("PUT", "/roles/:id"),
),
model.NewMenu("role-detail", "详情", "role",
model.NewPermission("GET", "/roles/:id"),
model.NewPermission("GET", "/menus"),
),
model.NewMenu("user-group", "用户组管理", "identity",
model.NewPermission("GET", "/user-groups/paging"),
),
model.NewMenu("user-group-add", "增加", "user-group",
model.NewPermission("POST", "/user-groups"),
),
model.NewMenu("user-group-del", "删除", "user-group",
model.NewPermission("DELETE", "/user-groups:/id"),
),
model.NewMenu("user-group-edit", "修改", "user-group",
model.NewPermission("GET", "/user-groups/:id"),
model.NewPermission("PUT", "/user-groups/:id"),
),
model.NewMenu("user-group-detail", "详情", "user-group",
model.NewPermission("GET", "/user-groups/:id"),
),
model.NewMenu("user-group-authorised-asset", "授权的资产", "user-group",
model.NewPermission("GET", "/authorised/assets/paging"),
),
model.NewMenu("user-group-bind-asset", "授权", "user-group-authorised-asset",
model.NewPermission("GET", "/authorised/selected"),
model.NewPermission("GET", "/assets"),
model.NewPermission("GET", "/strategies"),
),
model.NewMenu("user-group-unbind-asset", "移除", "user-group-authorised-asset",
model.NewPermission("DELETE", "/authorised/:id"),
),
model.NewMenu("authorised", "授权策略", "root"),
model.NewMenu("strategy", "授权策略", "authorised",
model.NewPermission("GET", "/strategies/paging"),
),
model.NewMenu("strategy-add", "增加", "strategy",
model.NewPermission("POST", "/strategies"),
),
model.NewMenu("strategy-edit", "修改", "strategy",
model.NewPermission("GET", "/strategies/:id"),
model.NewPermission("PUT", "/strategies/:id"),
),
model.NewMenu("strategy-del", "删除", "strategy",
model.NewPermission("DELETE", "/strategies/:id"),
),
model.NewMenu("strategy-detail", "详情", "strategy",
model.NewPermission("GET", "/strategies/:id"),
),
model.NewMenu("setting", "系统设置", "root",
model.NewPermission("GET", "/properties"),
model.NewPermission("PUT", "/properties"),
),
model.NewMenu("info", "个人中心", "root"),
}

View File

@ -5,43 +5,45 @@ import (
"errors"
"fmt"
"next-terminal/server/common/guacamole"
"next-terminal/server/env"
"next-terminal/server/guacd"
"next-terminal/server/model"
"next-terminal/server/repository"
"gorm.io/gorm"
)
var PropertyService = new(propertyService)
type propertyService struct {
baseService
}
var deprecatedPropertyNames = []string{
guacd.EnableDrive,
guacd.DrivePath,
guacd.DriveName,
guacd.DisableGlyphCaching,
guacd.CreateRecordingPath,
guacamole.EnableDrive,
guacamole.DrivePath,
guacamole.DriveName,
guacamole.DisableGlyphCaching,
guacamole.CreateRecordingPath,
}
var defaultProperties = map[string]string{
guacd.EnableRecording: "true",
guacd.FontName: "menlo",
guacd.FontSize: "12",
guacd.ColorScheme: "gray-black",
guacd.EnableWallpaper: "true",
guacd.EnableTheming: "true",
guacd.EnableFontSmoothing: "true",
guacd.EnableFullWindowDrag: "true",
guacd.EnableDesktopComposition: "true",
guacd.EnableMenuAnimations: "true",
guacd.DisableBitmapCaching: "false",
guacd.DisableOffscreenCaching: "false",
"cron-log-saved-limit": "360",
"login-log-saved-limit": "360",
"session-saved-limit": "360",
"user-default-storage-size": "5120",
guacamole.EnableRecording: "true",
guacamole.FontName: "menlo",
guacamole.FontSize: "12",
guacamole.ColorScheme: "gray-black",
guacamole.EnableWallpaper: "true",
guacamole.EnableTheming: "true",
guacamole.EnableFontSmoothing: "true",
guacamole.EnableFullWindowDrag: "true",
guacamole.EnableDesktopComposition: "true",
guacamole.EnableMenuAnimations: "true",
guacamole.DisableBitmapCaching: "false",
guacamole.DisableOffscreenCaching: "false",
"cron-log-saved-limit": "360",
"login-log-saved-limit": "360",
"session-saved-limit": "360",
"user-default-storage-size": "5120",
}
func (service propertyService) InitProperties() error {

268
server/service/role.go Normal file
View File

@ -0,0 +1,268 @@
package service
import (
"context"
"errors"
"sync"
"next-terminal/server/common/sets"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
var RoleService = new(roleService)
type roleService struct {
baseService
roleMenus sync.Map
}
func (s *roleService) Init() error {
ctx := context.Background()
// 创建默认的角色
if err := s.CreateDefaultRoles(); err != nil {
return err
}
// 重载角色对应权限的缓存
roles, err := repository.RoleRepository.FindAll(ctx)
if err != nil {
return err
}
for _, role := range roles {
refs, err := s.FindMenuByRoleId(ctx, role.ID)
if err != nil {
return err
}
var menus []string
for _, ref := range refs {
menus = append(menus, ref.MenuId)
}
s.setRoleMenus(role.ID, menus)
}
return nil
}
func (s *roleService) mapRoleMenus(keys []string) []model.RoleMenuRef {
var roleMenus []model.RoleMenuRef
for _, key := range keys {
roleMenus = append(roleMenus, model.RoleMenuRef{
MenuId: key,
Checked: true,
})
}
return roleMenus
}
func (s *roleService) Create(c context.Context, role *model.Role) error {
return s.Transaction(c, func(ctx context.Context) error {
if err := repository.RoleRepository.Create(ctx, role); err != nil {
return err
}
if err := s.createRolePermissionRefs(ctx, role); err != nil {
return err
}
return nil
})
}
func (s *roleService) createRolePermissionRefs(ctx context.Context, role *model.Role) error {
var menuIds = sets.NewStringSet()
var refIds = sets.NewStringSet()
var refs []*model.RoleMenuRef
for _, menu := range role.Menus {
refId := utils.Sign([]string{role.ID, menu.MenuId})
if refIds.Contains(refId) {
continue
}
ref := &model.RoleMenuRef{
ID: refId,
RoleId: role.ID,
MenuId: menu.MenuId,
Checked: menu.Checked,
}
refs = append(refs, ref)
refIds.Add(ref.ID)
menuIds.Add(menu.MenuId)
}
if err := repository.RoleMenuRefRepository.DeleteByIdIn(ctx, refIds.ToArray()); err != nil {
return err
}
if err := repository.RoleMenuRefRepository.CreateInBatches(ctx, refs); err != nil {
return err
}
s.setRoleMenus(role.ID, menuIds.ToArray())
return nil
}
func (s *roleService) UpdateById(c context.Context, role *model.Role, id string, force bool) error {
return s.Transaction(c, func(ctx context.Context) error {
dbRole, err := repository.RoleRepository.FindById(ctx, id)
if err != nil {
return err
}
if !force {
if !dbRole.Modifiable {
return errors.New("prohibit to modify " + dbRole.Name)
}
}
if err := repository.RoleRepository.UpdateById(ctx, role, id); err != nil {
return err
}
if err := repository.RoleMenuRefRepository.DeleteByRoleId(ctx, id); err != nil {
return err
}
if err := s.createRolePermissionRefs(ctx, role); err != nil {
return err
}
return nil
})
}
func (s *roleService) DeleteByIds(c context.Context, ids []string, force bool) error {
return s.Transaction(c, func(ctx context.Context) error {
for i := range ids {
id := ids[i]
if !force {
role, err := repository.RoleRepository.FindById(ctx, id)
if err != nil {
return err
}
if !role.Deletable {
return errors.New("prohibit to delete " + role.Name)
}
}
if err := repository.RoleRepository.DeleteById(ctx, id); err != nil {
return err
}
if err := repository.RoleMenuRefRepository.DeleteByRoleId(ctx, id); err != nil {
return err
}
if err := repository.UserRoleRefRepository.DeleteByRoleId(ctx, id); err != nil {
return err
}
// 删除缓存
s.removeRole(id)
}
return nil
})
}
func (s *roleService) FindById(ctx context.Context, id string) (*model.Role, error) {
role, err := repository.RoleRepository.FindById(ctx, id)
if err != nil {
return nil, err
}
permissions, err := s.FindMenuByRoleId(ctx, id)
if err != nil {
return nil, err
}
for i := range permissions {
permissions[i].ID = ""
permissions[i].RoleId = ""
}
role.Menus = permissions
return &role, nil
}
func (s *roleService) FindMenuByRoleId(ctx context.Context, id string) ([]model.RoleMenuRef, error) {
refs, err := repository.RoleMenuRefRepository.FindByRoleId(ctx, id)
if err != nil {
return nil, err
}
return refs, nil
}
func (s *roleService) GetRolesByUserId(userId string) ([]string, error) {
refs, err := repository.UserRoleRefRepository.FindByUserId(context.Background(), userId)
if err != nil {
return nil, err
}
var roles []string
for _, ref := range refs {
roles = append(roles, ref.RoleId)
}
return roles, nil
}
func (s *roleService) GetMenuListByRole(role string) []string {
value, ok := s.roleMenus.Load(role)
if ok {
return value.([]string)
}
return nil
}
func (s *roleService) setRoleMenus(role string, items []string) {
s.roleMenus.Store(role, items)
}
func (s *roleService) removeRole(role string) {
s.roleMenus.Delete(role)
}
func (s *roleService) CreateDefaultRoles() error {
var menus []string
for _, menu := range DefaultMenu {
menus = append(menus, menu.ID)
}
var auditPermissions = []string{
"dashboard",
"log-audit",
"online-session",
"offline-session",
"login-log",
"online-session-paging",
"online-session-disconnect",
"online-session-monitor",
"offline-session-paging",
"offline-session-playback",
"offline-session-del",
"offline-session-clear",
"offline-session-reviewed",
"offline-session-unreviewed",
"offline-session-reviewed-all",
"login-log-paging",
"login-log-del",
"login-log-clear",
}
var securityPermissions = []string{
"security",
"access-security-paging",
"access-security-add",
"access-security-edit",
"access-security-del",
}
var DefaultRoles = []*model.Role{
model.NewRole("system-administrator", "系统管理员", "default", false, false, s.mapRoleMenus(menus)),
model.NewRole("audit-administrator", "审计管理员", "default", false, false, s.mapRoleMenus(auditPermissions)),
model.NewRole("security-administrator", "安全管理员", "default", false, false, s.mapRoleMenus(securityPermissions)),
}
ctx := context.Background()
for _, role := range DefaultRoles {
exists, err := repository.RoleRepository.ExistsById(ctx, role.ID)
if err != nil {
return err
}
if exists {
if err := s.UpdateById(ctx, role, role.ID, true); err != nil {
return err
}
continue
}
if err := s.Create(ctx, role); err != nil {
return err
}
}
return nil
}

View File

@ -7,6 +7,8 @@ import (
"next-terminal/server/repository"
)
var SecurityService = new(securityService)
type securityService struct{}
func (service securityService) ReloadAccessSecurity() error {

View File

@ -4,14 +4,17 @@ import (
"context"
"encoding/base64"
"errors"
"next-terminal/server/common/nt"
"os"
"path"
"strconv"
"sync"
"next-terminal/server/common"
"next-terminal/server/common/guacamole"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/env"
"next-terminal/server/global/session"
"next-terminal/server/guacd"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
@ -20,12 +23,14 @@ import (
"gorm.io/gorm"
)
var SessionService = new(sessionService)
type sessionService struct {
baseService
}
func (service sessionService) FixSessionState() error {
sessions, err := repository.SessionRepository.FindByStatus(context.TODO(), constant.Connected)
sessions, err := repository.SessionRepository.FindByStatus(context.TODO(), nt.Connected)
if err != nil {
return err
}
@ -33,8 +38,8 @@ func (service sessionService) FixSessionState() error {
if len(sessions) > 0 {
for i := range sessions {
s := model.Session{
Status: constant.Disconnected,
DisconnectedTime: utils.NowJsonTime(),
Status: nt.Disconnected,
DisconnectedTime: common.NowJsonTime(),
}
_ = repository.SessionRepository.UpdateById(context.TODO(), &s, sessions[i].ID)
@ -48,7 +53,7 @@ func (service sessionService) EmptyPassword() error {
}
func (service sessionService) ClearOfflineSession() error {
sessions, err := repository.SessionRepository.FindByStatus(context.TODO(), constant.Disconnected)
sessions, err := repository.SessionRepository.FindByStatus(context.TODO(), nt.Disconnected)
if err != nil {
return err
}
@ -56,7 +61,20 @@ func (service sessionService) ClearOfflineSession() error {
for i := range sessions {
sessionIds = append(sessionIds, sessions[i].ID)
}
return repository.SessionRepository.DeleteByIds(context.TODO(), sessionIds)
return service.DeleteByIds(context.TODO(), sessionIds)
}
func (service sessionService) DeleteByIds(c context.Context, sessionIds []string) error {
recordingPath := config.GlobalCfg.Guacd.Recording
for i := range sessionIds {
if err := os.RemoveAll(path.Join(recordingPath, sessionIds[i])); err != nil {
return err
}
if err := repository.SessionRepository.DeleteById(c, sessionIds[i]); err != nil {
return err
}
}
return nil
}
func (service sessionService) ReviewedAll() error {
@ -92,13 +110,13 @@ func (service sessionService) CloseSessionById(sessionId string, code int, reaso
defer mutex.Unlock()
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession != nil {
log.Debugf("[%v] 会话关闭,原因:%v", sessionId, reason)
log.Debug("会话关闭", log.String("会话ID", sessionId), log.String("原因", reason))
service.WriteCloseMessage(nextSession, nextSession.Mode, code, reason)
if nextSession.Observer != nil {
nextSession.Observer.Range(func(key string, ob *session.Session) {
service.WriteCloseMessage(ob, ob.Mode, code, reason)
log.Debugf("[%v] 强制踢出会话的观察者: %v", sessionId, ob.ID)
log.Debug("强制踢出会话的观察者", log.String("会话ID", sessionId))
})
}
}
@ -109,12 +127,12 @@ func (service sessionService) CloseSessionById(sessionId string, code int, reaso
func (service sessionService) WriteCloseMessage(sess *session.Session, mode string, code int, reason string) {
switch mode {
case constant.Guacd:
err := guacd.NewInstruction("error", "", strconv.Itoa(code))
case nt.Guacd:
err := guacamole.NewInstruction("error", "", strconv.Itoa(code))
_ = sess.WriteString(err.String())
disconnect := guacd.NewInstruction("disconnect")
disconnect := guacamole.NewInstruction("disconnect")
_ = sess.WriteString(disconnect.String())
case constant.Native, constant.Terminal:
case nt.Native, nt.Terminal:
msg := `0` + reason
_ = sess.WriteString(msg)
}
@ -128,11 +146,11 @@ func (service sessionService) DisDBSess(sessionId string, code int, reason strin
return err
}
if s.Status == constant.Disconnected {
return err
if s.Status == nt.Disconnected {
return nil
}
if s.Status == constant.Connecting {
if s.Status == nt.Connecting {
// 会话还未建立成功,无需保留数据
if err := repository.SessionRepository.DeleteById(c, sessionId); err != nil {
return err
@ -142,8 +160,8 @@ func (service sessionService) DisDBSess(sessionId string, code int, reason strin
ss := model.Session{}
ss.ID = sessionId
ss.Status = constant.Disconnected
ss.DisconnectedTime = utils.NowJsonTime()
ss.Status = nt.Disconnected
ss.DisconnectedTime = common.NowJsonTime()
ss.Code = code
ss.Message = reason
ss.Password = "-"
@ -206,6 +224,13 @@ func (service sessionService) Decrypt(item *model.Session) error {
return nil
}
func (service sessionService) renderBoolToStr(b *bool) string {
if *(b) == true {
return "1"
}
return "0"
}
func (service sessionService) Create(clientIp, assetId, mode string, user *model.User) (*model.Session, error) {
asset, err := repository.AssetRepository.FindById(context.TODO(), assetId)
if err != nil {
@ -223,16 +248,17 @@ func (service sessionService) Create(clientIp, assetId, mode string, user *model
paste = "1"
)
if asset.Owner != user.ID && constant.TypeUser == user.Type {
if asset.Owner != user.ID && nt.TypeUser == user.Type {
// 普通用户访问非自己创建的资产需要校验权限
resourceSharers, err := repository.ResourceSharerRepository.FindByResourceIdAndUserId(context.TODO(), assetId, user.ID)
authorised, err := AuthorisedService.GetAuthorised(user.ID, assetId)
if err != nil {
return nil, err
}
if len(resourceSharers) == 0 {
if authorised == nil || authorised.ID == "" {
return nil, errors.New("您没有权限访问此资产")
}
strategyId := resourceSharers[0].StrategyId
strategyId := authorised.StrategyId
if strategyId != "" {
strategy, err := repository.StrategyRepository.FindById(context.TODO(), strategyId)
if err != nil {
@ -240,26 +266,26 @@ func (service sessionService) Create(clientIp, assetId, mode string, user *model
return nil, err
}
} else {
upload = strategy.Upload
download = strategy.Download
_delete = strategy.Delete
rename = strategy.Rename
edit = strategy.Edit
_copy = strategy.Copy
paste = strategy.Paste
upload = service.renderBoolToStr(strategy.Upload)
download = service.renderBoolToStr(strategy.Download)
_delete = service.renderBoolToStr(strategy.Delete)
rename = service.renderBoolToStr(strategy.Rename)
edit = service.renderBoolToStr(strategy.Edit)
_copy = service.renderBoolToStr(strategy.Copy)
paste = service.renderBoolToStr(strategy.Paste)
}
}
}
var storageId = ""
if constant.RDP == asset.Protocol {
if nt.RDP == asset.Protocol {
attr, err := repository.AssetRepository.FindAssetAttrMapByAssetId(context.TODO(), assetId)
if err != nil {
return nil, err
}
if "true" == attr[guacd.EnableDrive] {
if "true" == attr[guacamole.EnableDrive] {
fileSystem = "1"
storageId = attr[guacd.DrivePath]
storageId = attr[guacamole.DrivePath]
if storageId == "" {
storageId = user.ID
}
@ -302,7 +328,7 @@ func (service sessionService) Create(clientIp, assetId, mode string, user *model
Protocol: asset.Protocol,
IP: asset.IP,
Port: asset.Port,
Status: constant.NoConnect,
Status: nt.NoConnect,
ClientIP: clientIp,
Mode: mode,
FileSystem: fileSystem,
@ -317,7 +343,7 @@ func (service sessionService) Create(clientIp, assetId, mode string, user *model
AccessGatewayId: asset.AccessGatewayId,
Reviewed: false,
}
if constant.Anonymous != user.Type {
if nt.Anonymous != user.Type {
s.Creator = user.ID
}
@ -327,7 +353,7 @@ func (service sessionService) Create(clientIp, assetId, mode string, user *model
return nil, err
}
if credential.Type == constant.Custom {
if credential.Type == nt.Custom {
s.Username = credential.Username
s.Password = credential.Password
} else {

View File

@ -14,8 +14,8 @@ import (
"strconv"
"strings"
"next-terminal/server/common"
"next-terminal/server/config"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
@ -24,6 +24,8 @@ import (
"gorm.io/gorm"
)
var StorageService = new(storageService)
type storageService struct {
}
@ -72,7 +74,6 @@ func (service storageService) InitStorages() error {
if err := os.MkdirAll(storageDir, os.ModePerm); err != nil {
return err
}
log.Infof("创建storage:「%v」文件夹: %v", storage.Name, storageDir)
}
}
return nil
@ -102,13 +103,12 @@ func (service storageService) CreateStorageByUser(c context.Context, user *model
IsDefault: true,
LimitSize: limitSize,
Owner: user.ID,
Created: utils.NowJsonTime(),
Created: common.NowJsonTime(),
}
storageDir := path.Join(drivePath, storage.ID)
if err := os.MkdirAll(storageDir, os.ModePerm); err != nil {
return err
}
log.Infof("创建storage:「%v」文件夹: %v", storage.Name, storageDir)
err = repository.StorageRepository.Create(c, &storage)
if err != nil {
_ = os.RemoveAll(storageDir)
@ -118,13 +118,13 @@ func (service storageService) CreateStorageByUser(c context.Context, user *model
}
type File struct {
Name string `json:"name"`
Path string `json:"path"`
IsDir bool `json:"isDir"`
Mode string `json:"mode"`
IsLink bool `json:"isLink"`
ModTime utils.JsonTime `json:"modTime"`
Size int64 `json:"size"`
Name string `json:"name"`
Path string `json:"path"`
IsDir bool `json:"isDir"`
Mode string `json:"mode"`
IsLink bool `json:"isLink"`
ModTime common.JsonTime `json:"modTime"`
Size int64 `json:"size"`
}
func (service storageService) Ls(drivePath, remoteDir string) ([]File, error) {
@ -141,7 +141,7 @@ func (service storageService) Ls(drivePath, remoteDir string) ([]File, error) {
IsDir: fileInfos[i].IsDir(),
Mode: fileInfos[i].Mode().String(),
IsLink: fileInfos[i].Mode()&os.ModeSymlink == os.ModeSymlink,
ModTime: utils.NewJsonTime(fileInfos[i].ModTime()),
ModTime: common.NewJsonTime(fileInfos[i].ModTime()),
Size: fileInfos[i].Size(),
}

View File

@ -0,0 +1,29 @@
package service
import (
"context"
"next-terminal/server/common"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
var StorageLogService = new(storageLogService)
type storageLogService struct {
baseService
}
func (s storageLogService) Save(ctx context.Context, assetId, sessionId, userId, action, filename string) error {
storageLog := &model.StorageLog{
ID: utils.UUID(),
AssetId: assetId,
SessionId: sessionId,
UserId: userId,
Action: action,
FileName: filename,
Created: common.NowJsonTime(),
}
return repository.StorageLogRepository.Create(ctx, storageLog)
}

View File

@ -3,9 +3,11 @@ package service
import (
"errors"
"fmt"
"next-terminal/server/common/nt"
"strings"
"next-terminal/server/constant"
"next-terminal/server/branding"
"next-terminal/server/common"
"next-terminal/server/dto"
"next-terminal/server/env"
"next-terminal/server/global/cache"
@ -18,6 +20,10 @@ import (
"gorm.io/gorm"
)
const SuperAdminID = `abcdefghijklmnopqrstuvwxyz`
var UserService = new(userService)
type userService struct {
baseService
}
@ -37,37 +43,52 @@ func (service userService) InitUser() (err error) {
}
user := model.User{
ID: utils.UUID(),
ID: SuperAdminID,
Username: "admin",
Password: string(pass),
Nickname: "超级管理员",
Type: constant.TypeAdmin,
Created: utils.NowJsonTime(),
Status: constant.StatusEnabled,
Type: nt.TypeAdmin,
Created: common.NowJsonTime(),
Status: nt.StatusEnabled,
}
if err := repository.UserRepository.Create(context.TODO(), &user); err != nil {
return err
}
log.Infof("初始用户创建成功,账号:「%v」密码「%v」", user.Username, initPassword)
} else {
for i := range users {
// 修正默认用户类型为管理员
if users[i].Type == "" {
user := model.User{
Type: constant.TypeAdmin,
Type: nt.TypeUser,
ID: users[i].ID,
}
if err := repository.UserRepository.Update(context.TODO(), &user); err != nil {
return err
}
log.Infof("自动修正用户「%v」id「%v」类型为管理员", users[i].Nickname, users[i].ID)
}
if users[i].Type == nt.TypeAdmin {
roles, err := RoleService.GetRolesByUserId(users[i].ID)
if err != nil {
return err
}
if len(roles) == 0 {
users[i].Roles = []string{"system-administrator"}
if err := service.saveUserRoles(context.Background(), users[i]); err != nil {
return err
}
}
}
}
}
return nil
}
func (service userService) IsSuperAdmin(userId string) bool {
return SuperAdminID == userId
}
func (service userService) FixUserOnlineState() error {
// 修正用户登录状态
onlineUsers, err := repository.UserRepository.FindOnlineUsers(context.TODO())
@ -100,7 +121,7 @@ func (service userService) LogoutByToken(token string) (err error) {
return err
}
loginLogForUpdate := &model.LoginLog{LogoutTime: utils.NowJsonTime(), ID: token}
loginLogForUpdate := &model.LoginLog{LogoutTime: common.NowJsonTime(), ID: token}
err = repository.LoginLogRepository.Update(context.TODO(), loginLogForUpdate)
if err != nil {
return err
@ -153,18 +174,15 @@ func (service userService) GetUserLoginToken(c context.Context, username string)
func (service userService) OnEvicted(token string, value interface{}) {
if strings.HasPrefix(token, "forever") {
log.Debugf("re gen forever token")
} else {
log.Debugf("用户Token「%v」过期", token)
err := service.LogoutByToken(token)
if err != nil && !errors.Is(gorm.ErrRecordNotFound, err) {
log.Errorf("退出登录失败 %v", err)
}
}
}
func (service userService) UpdateStatusById(id string, status string) error {
if constant.StatusDisabled == status {
if nt.StatusDisabled == status {
// 将该用户下线
if err := service.LogoutById(context.TODO(), id); err != nil {
return err
@ -197,7 +215,7 @@ func (service userService) ReloadToken() error {
authorization := dto.Authorization{
Token: token,
Type: constant.LoginToken,
Type: nt.LoginToken,
Remember: loginLog.Remember,
User: &user,
}
@ -208,7 +226,7 @@ func (service userService) ReloadToken() error {
} else {
cache.TokenManager.Set(token, authorization, cache.NotRememberExpiration)
}
log.Debugf("重新加载用户「%v」授权Token「%v」到缓存", user.Nickname, token)
log.Debug("重新加载用户授权Token", log.String("username", user.Nickname), log.String("token", token))
}
return nil
}
@ -232,19 +250,21 @@ func (service userService) CreateUser(user model.User) (err error) {
user.Password = string(pass)
user.ID = utils.UUID()
user.Created = utils.NowJsonTime()
user.Status = constant.StatusEnabled
user.Created = common.NowJsonTime()
user.Status = nt.StatusEnabled
if err := repository.UserRepository.Create(c, &user); err != nil {
return err
}
err = StorageService.CreateStorageByUser(c, &user)
if err != nil {
if err := service.saveUserRoles(c, user); err != nil {
return err
}
if err := StorageService.CreateStorageByUser(c, &user); err != nil {
return err
}
if user.Mail != "" {
subject := fmt.Sprintf("%s 注册通知", constant.AppName)
subject := fmt.Sprintf("%s 注册通知", branding.Name)
text := fmt.Sprintf(`您好,%s。
管理员为你开通了账户。
账号:%s
@ -257,6 +277,20 @@ func (service userService) CreateUser(user model.User) (err error) {
}
func (service userService) saveUserRoles(c context.Context, user model.User) error {
for _, role := range user.Roles {
ref := &model.UserRoleRef{
ID: utils.UUID(),
UserId: user.ID,
RoleId: role,
}
if err := repository.UserRoleRefRepository.Create(c, ref); err != nil {
return err
}
}
return nil
}
func (service userService) DeleteUserById(userId string) error {
user, err := repository.UserRepository.FindById(context.TODO(), userId)
if err != nil {
@ -276,14 +310,17 @@ func (service userService) DeleteUserById(userId string) error {
return err
}
// 删除用户与资产的关系
if err := repository.ResourceSharerRepository.DeleteByUserId(c, userId); err != nil {
if err := repository.AuthorisedRepository.DeleteByUserId(c, userId); err != nil {
return err
}
// 删除用户的默认磁盘空间
if err := StorageService.DeleteStorageById(c, userId, true); err != nil {
return err
}
// 删除用户与角色的关系
if err := repository.UserRoleRefRepository.DeleteByUserId(c, user.ID); err != nil {
return err
}
// 删除用户
if err := repository.UserRepository.DeleteById(c, userId); err != nil {
return err
@ -324,7 +361,7 @@ func (service userService) SaveLoginLog(clientIP, clientUserAgent string, userna
Username: username,
ClientIP: clientIP,
ClientUserAgent: clientUserAgent,
LoginTime: utils.NowJsonTime(),
LoginTime: common.NowJsonTime(),
Reason: reason,
Remember: remember,
}
@ -343,7 +380,7 @@ func (service userService) SaveLoginLog(clientIP, clientUserAgent string, userna
}
func (service userService) DeleteALlLdapUser(ctx context.Context) error {
return repository.UserRepository.DeleteBySource(ctx, constant.SourceLdap)
return repository.UserRepository.DeleteBySource(ctx, nt.SourceLdap)
}
func (service userService) UpdateUser(id string, user model.User) error {
@ -367,41 +404,83 @@ func (service userService) UpdateUser(id string, user model.User) error {
}
}
if err := repository.UserRoleRefRepository.DeleteByUserId(ctx, user.ID); err != nil {
return err
}
if err := service.saveUserRoles(ctx, user); err != nil {
return err
}
// 移除用户角色的缓存
cache.UserRolesManager.Delete(id)
return repository.UserRepository.Update(ctx, &user)
})
}
func (service userService) AddSharerResources(ctx context.Context, userGroupId, userId, strategyId, resourceType string, resourceIds []string) error {
if service.InTransaction(ctx) {
return service.addSharerResources(ctx, resourceIds, userGroupId, userId, strategyId, resourceType)
} else {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
ctx2 := service.Context(tx)
return service.addSharerResources(ctx2, resourceIds, userGroupId, userId, strategyId, resourceType)
})
func (service userService) FindById(id string) (*model.User, error) {
item, err := repository.UserRepository.FindById(context.TODO(), id)
if err != nil {
return nil, err
}
roles, err := RoleService.GetRolesByUserId(id)
if err != nil {
return nil, err
}
item.Roles = roles
return &item, nil
}
func (service userService) addSharerResources(ctx context.Context, resourceIds []string, userGroupId string, userId string, strategyId string, resourceType string) error {
for i := range resourceIds {
resourceId := resourceIds[i]
// 保证同一个资产只能分配给一个用户或者组
id := utils.Sign([]string{resourceId, resourceType, userId, userGroupId})
if err := repository.ResourceSharerRepository.DeleteById(ctx, id); err != nil {
return err
func (service userService) ResetTotp(ids []string) error {
return service.Transaction(context.Background(), func(ctx context.Context) error {
for _, id := range ids {
u := &model.User{
TOTPSecret: "-",
ID: id,
}
if err := repository.UserRepository.Update(ctx, u); err != nil {
return err
}
}
rs := &model.ResourceSharer{
ID: id,
ResourceId: resourceId,
ResourceType: resourceType,
StrategyId: strategyId,
UserId: userId,
UserGroupId: userGroupId,
}
if err := repository.ResourceSharerRepository.AddSharerResource(ctx, rs); err != nil {
return err
}
}
return nil
return nil
})
}
func (service userService) ChangePassword(ids []string, password string) error {
passwd, err := utils.Encoder.Encode([]byte(password))
if err != nil {
return err
}
return service.Transaction(context.Background(), func(ctx context.Context) error {
for _, id := range ids {
u := &model.User{
Password: string(passwd),
ID: id,
}
if err := repository.UserRepository.Update(ctx, u); err != nil {
return err
}
user, err := repository.UserRepository.FindById(ctx, id)
if err != nil {
return err
}
if user.Mail != "" {
subject := "密码修改通知"
text := fmt.Sprintf(`您好,%s。
管理员已将你的密码修改为:%s。
`, user.Username, password)
go MailService.SendMail(user.Mail, subject, text)
}
}
return nil
})
}

View File

@ -3,7 +3,8 @@ package service
import (
"context"
"next-terminal/server/constant"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"next-terminal/server/env"
"next-terminal/server/model"
"next-terminal/server/repository"
@ -12,6 +13,8 @@ import (
"gorm.io/gorm"
)
var UserGroupService = new(userGroupService)
type userGroupService struct {
baseService
}
@ -28,7 +31,7 @@ func (service userGroupService) DeleteById(userGroupId string) error {
return err
}
// 删除用户组与资产的关系
if err := repository.ResourceSharerRepository.DeleteByUserGroupId(c, userGroupId); err != nil {
if err := repository.AuthorisedRepository.DeleteByUserGroupId(c, userGroupId); err != nil {
return err
}
return nil
@ -42,43 +45,34 @@ func (service userGroupService) Create(ctx context.Context, name string, members
}
if exist {
return model.UserGroup{}, constant.ErrNameAlreadyUsed
return model.UserGroup{}, nt.ErrNameAlreadyUsed
}
userGroupId := utils.UUID()
userGroup := model.UserGroup{
ID: userGroupId,
Created: utils.NowJsonTime(),
Created: common.NowJsonTime(),
Name: name,
}
if service.InTransaction(ctx) {
return userGroup, service.create(ctx, userGroup, members, userGroupId)
} else {
return userGroup, env.GetDB().Transaction(func(tx *gorm.DB) error {
c := service.Context(tx)
return service.create(c, userGroup, members, userGroupId)
})
}
}
func (service userGroupService) create(c context.Context, userGroup model.UserGroup, members []string, userGroupId string) error {
if err := repository.UserGroupRepository.Create(c, &userGroup); err != nil {
return err
}
if len(members) > 0 {
for _, member := range members {
userGroupMember := model.UserGroupMember{
ID: utils.Sign([]string{userGroupId, member}),
UserId: member,
UserGroupId: userGroupId,
}
if err := repository.UserGroupMemberRepository.Create(c, &userGroupMember); err != nil {
return err
return userGroup, service.Transaction(ctx, func(ctx context.Context) error {
if err := repository.UserGroupRepository.Create(ctx, &userGroup); err != nil {
return err
}
if len(members) > 0 {
for _, member := range members {
userGroupMember := model.UserGroupMember{
ID: utils.Sign([]string{userGroupId, member}),
UserId: member,
UserGroupId: userGroupId,
}
if err := repository.UserGroupMemberRepository.Create(ctx, &userGroupMember); err != nil {
return err
}
}
}
}
return nil
return nil
})
}
func (service userGroupService) Update(userGroupId string, name string, members []string) (err error) {
@ -94,7 +88,7 @@ func (service userGroupService) Update(userGroupId string, name string, members
}
if exist {
return constant.ErrNameAlreadyUsed
return nt.ErrNameAlreadyUsed
}
}

View File

@ -1,17 +0,0 @@
package service
var (
AssetService = new(assetService)
BackupService = new(backupService)
CredentialService = new(credentialService)
GatewayService = new(gatewayService)
JobService = new(jobService)
MailService = new(mailService)
PropertyService = new(propertyService)
SecurityService = new(securityService)
SessionService = new(sessionService)
StorageService = new(storageService)
UserService = new(userService)
UserGroupService = new(userGroupService)
AccessTokenService = new(accessTokenService)
)

84
server/service/worker.go Normal file
View File

@ -0,0 +1,84 @@
package service
import (
"context"
"next-terminal/server/common/sets"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
var WorkerService = &workerService{}
type workerService struct {
}
func (s *workerService) FindMyAssetPaging(pageIndex, pageSize int, name, protocol, tags string, userId string, order, field string) (o []model.AssetForPage, total int64, err error) {
assetIdList, err := s.getAssetIdListByUserId(userId)
if err != nil {
return nil, 0, err
}
items, total, err := repository.AssetRepository.FindMyAssets(context.Background(), pageIndex, pageSize, name, protocol, tags, assetIdList, order, field)
if err != nil {
return nil, 0, err
}
return items, total, nil
}
func (s *workerService) FindMyAsset(name, protocol, tags string, userId string, order, field string) (o []model.AssetForPage, err error) {
assetIdList, err := s.getAssetIdListByUserId(userId)
if err != nil {
return nil, err
}
items, _, err := repository.AssetRepository.FindMyAssets(context.Background(), 1, 1000, name, protocol, tags, assetIdList, order, field)
if err != nil {
return nil, err
}
return items, nil
}
func (s *workerService) FindMyAssetTags(ctx context.Context, userId string) ([]string, error) {
assetIdList, err := s.getAssetIdListByUserId(userId)
if err != nil {
return nil, err
}
tags, err := repository.AssetRepository.FindMyAssetTags(ctx, assetIdList)
return tags, err
}
func (s *workerService) getAssetIdListByUserId(userId string) ([]string, error) {
set := sets.NewStringSet()
authorisedByUser, err := repository.AuthorisedRepository.FindByUserId(context.Background(), userId)
if err != nil {
return nil, err
}
for _, authorised := range authorisedByUser {
set.Add(authorised.AssetId)
}
userGroupIds, err := repository.UserGroupMemberRepository.FindUserGroupIdsByUserId(context.Background(), userId)
if err != nil {
return nil, err
}
authorisedByUserGroup, err := repository.AuthorisedRepository.FindByUserGroupIdIn(context.Background(), userGroupIds)
if err != nil {
return nil, err
}
for _, authorised := range authorisedByUserGroup {
set.Add(authorised.AssetId)
}
return set.ToArray(), nil
}
func (s *workerService) CheckPermission(assetId, userId string) (bool, error) {
assetIdList, err := s.getAssetIdListByUserId(userId)
if err != nil {
return false, err
}
return utils.Contains(assetIdList, assetId), nil
}