Files
vm/object/session.go
zooqueenandHanzo Dev a121f8d0d0 refactor: rebrand from casvisor to hanzoai/visor, remove hardcoded credentials
- Rename Go module from github.com/casvisor/casvisor to github.com/hanzoai/visor
- Update all copyright headers from casbin to Hanzo Industries Inc
- Remove all hardcoded passwords and credentials from config files
- All credentials now sourced from environment variables / Hanzo KMS
- Switch default database from MySQL to PostgreSQL (matching production)
- Rename binary output from server/casvisor to visor
- Update Dockerfile, build.sh, docker-compose.yml, docker-entrypoint.sh
- Replace casdoor* config key references with iam* equivalents
- Update CI to use PostgreSQL service and mark CI-only test password
- Add visor and casvisor binaries to .gitignore
- Add envFrom secretRef for visor-env in K8s deployment
- Add ExternalSecret template for Hanzo KMS credential provisioning

Co-authored-by: Hanzo Dev <[email protected]>
2026-02-21 00:04:31 -08:00

271 lines
7.0 KiB
Go

// Copyright 2024 Hanzo Industries Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"strconv"
"sync"
"github.com/hanzoai/visor/util"
"github.com/hanzoai/visor/util/guacamole"
"xorm.io/core"
)
const (
NoConnect = "no_connect"
Connecting = "connecting"
Connected = "connected"
Disconnected = "disconnected"
)
type Session struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
StartTime string `xorm:"varchar(100)" json:"startTime"`
EndTime string `xorm:"varchar(100)" json:"endTime"`
Protocol string `xorm:"varchar(20)" json:"protocol"`
ConnectionId string `xorm:"varchar(50)" json:"connectionId"`
Asset string `xorm:"varchar(200) index" json:"asset"`
Creator string `xorm:"varchar(36) index" json:"creator"`
ClientIp string `xorm:"varchar(200)" json:"clientIp"`
UserAgent string `xorm:"varchar(200)" json:"userAgent"`
ClientIpDesc string `xorm:"varchar(100)" json:"clientIpDesc"`
UserAgentDesc string `xorm:"varchar(100)" json:"userAgentDesc"`
Width int `json:"width"`
Height int `json:"height"`
Status string `xorm:"varchar(20) index" json:"status"`
Recording string `xorm:"varchar(1000)" json:"recording"`
Code int `json:"code"`
Message string `json:"message"`
Mode string `xorm:"varchar(10)" json:"mode"`
Operations []string `xorm:"json varchar(1000)" json:"operations"`
Reviewed bool `json:"reviewed"`
CommandCount int64 `json:"commandCount"`
}
func (s *Session) GetId() string {
return util.GetIdFromOwnerAndName(s.Owner, s.Name)
}
func GetSessionCount(owner, status, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Session{Status: status})
}
func GetSessions(owner string) ([]*Session, error) {
sessions := []*Session{}
err := adapter.engine.Desc("connected_time").Find(&sessions, &Session{Owner: owner})
if err != nil {
return sessions, err
}
return sessions, nil
}
func GetPaginationSessions(owner, status string, offset, limit int, field, value, sortField, sortOrder string) ([]*Session, error) {
sessions := []*Session{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&sessions, &Session{Status: status})
if err != nil {
return sessions, err
}
return sessions, nil
}
func GetSessionsByStatus(statuses []string) ([]*Session, error) {
sessions := []*Session{}
err := adapter.engine.In("status", statuses).Find(&sessions)
if err != nil {
return sessions, err
}
return sessions, nil
}
func getSession(owner string, name string) (*Session, error) {
if owner == "" || name == "" {
return nil, nil
}
session := Session{Owner: owner, Name: name}
existed, err := adapter.engine.Get(&session)
if err != nil {
return &session, err
}
if existed {
return &session, nil
} else {
return nil, nil
}
}
func GetConnSession(id string) (*Session, error) {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
return getSession(owner, name)
}
func UpdateSession(id string, session *Session, columns ...string) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
if oldSession, err := getSession(owner, name); err != nil {
return false, err
} else if oldSession == nil {
return false, nil
}
if len(columns) == 0 {
_, err := adapter.engine.ID(core.PK{owner, name}).AllCols().Update(session)
if err != nil {
return false, err
}
} else {
_, err := adapter.engine.ID(core.PK{owner, name}).Cols(columns...).Update(session)
if err != nil {
return false, err
}
}
return true, nil
}
func DeleteSession(session *Session) (bool, error) {
affected, err := adapter.engine.ID(core.PK{session.Owner, session.Name}).Delete(&Session{})
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteSessionById(id string) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return DeleteSession(&Session{Owner: owner, Name: name})
}
func AddSession(session *Session) (bool, error) {
affected, err := adapter.engine.Insert(session)
if err != nil {
return false, err
}
return affected != 0, nil
}
func CreateSession(session *Session, machineId string, mode string) (*Session, error) {
machine, err := GetMachine(machineId)
if err != nil {
return nil, err
}
if machine == nil {
return nil, nil
}
session.Owner = machine.Owner
session.Name = util.GenerateId()
session.CreatedTime = util.GetCurrentTime()
session.Protocol = machine.RemoteProtocol
session.Asset = machineId
session.Status = NoConnect
session.Mode = mode
session.Reviewed = false
session.Operations = []string{"paste", "copy", "createDir", "edit", "rename", "delete", "download", "upload", "fileSystem"}
_, err = AddSession(session)
if err != nil {
return nil, err
}
respSession := &Session{
Owner: session.Owner,
Name: session.Name,
Protocol: machine.RemoteProtocol,
Operations: session.Operations,
}
return respSession, nil
}
func CloseDBSession(id string, code int, msg string) error {
s, err := GetConnSession(id)
if err != nil {
return err
}
if s == nil {
return nil
}
if s.Status == Disconnected {
return nil
}
if s.Status == Connecting {
// The session has not been established successfully, so you do not need to save data
_, err := DeleteSession(s)
if err != nil {
return err
}
return nil
}
s.Status = Disconnected
s.Code = code
s.Message = msg
s.EndTime = util.GetCurrentTime()
_, err = UpdateSession(id, s)
if err != nil {
return err
}
return nil
}
func WriteCloseMessage(session *guacamole.Session, mode string, code int, msg string) {
err := guacamole.NewInstruction("error", "", strconv.Itoa(code))
_ = session.WriteString(err.String())
disconnect := guacamole.NewInstruction("disconnect")
_ = session.WriteString(disconnect.String())
}
var mutex sync.Mutex
func CloseSession(id string, code int, msg string) error {
mutex.Lock()
defer mutex.Unlock()
guacSession := guacamole.GlobalSessionManager.Get(id)
if guacSession != nil {
WriteCloseMessage(guacSession, guacSession.Mode, code, msg)
if guacSession.Observer != nil {
guacSession.Observer.Range(func(key string, ob *guacamole.Session) {
WriteCloseMessage(ob, ob.Mode, code, msg)
})
}
}
guacamole.GlobalSessionManager.Delete(id)
err := CloseDBSession(id, code, msg)
if err != nil {
return err
}
return nil
}