feat: team access control

This commit is contained in:
2024-11-12 17:17:10 +00:00
parent f5250d5361
commit 2d4c81e15d
31 changed files with 410 additions and 161 deletions
+15 -1
View File
@@ -54,7 +54,21 @@ func login(c *fiber.Ctx) error {
func getUser(c *fiber.Ctx) error {
user := utils.GetUser(c)
return c.JSON(user)
teams := []TeamWithRole{}
for _, item := range user.Teams {
teams = append(teams, TeamWithRole{
ID: item.TeamID,
Name: item.Team.Name,
Icon: item.Team.Icon,
Role: item.Role,
})
}
return c.JSON(&GetUserResult{
AuthUser: *user,
Teams: teams,
})
}
func logout(c *fiber.Ctx) error {
+14
View File
@@ -1,6 +1,20 @@
package auth
import "rul.sh/vaulterm/middleware"
type LoginSchema struct {
Username string `json:"username"`
Password string `json:"password"`
}
type TeamWithRole struct {
ID string `json:"id"`
Name string `json:"name"`
Icon string `json:"icon"`
Role string `json:"role"`
}
type GetUserResult struct {
middleware.AuthUser
Teams []TeamWithRole `json:"teams"`
}
+12 -19
View File
@@ -20,8 +20,14 @@ func NewRepository(r *Hosts) *Hosts {
return r
}
func (r *Hosts) GetAll() ([]*models.Host, error) {
query := r.ACL(r.db.Order("id DESC"))
func (r *Hosts) GetAll(opt GetAllOpt) ([]*models.Host, error) {
query := r.db.Order("id DESC")
if opt.TeamID != "" {
query = query.Where("hosts.team_id = ?", opt.TeamID)
} else {
query = query.Where("hosts.owner_id = ? AND hosts.team_id IS NULL", r.User.ID)
}
var rows []*models.Host
ret := query.Find(&rows)
@@ -30,10 +36,8 @@ func (r *Hosts) GetAll() ([]*models.Host, error) {
}
func (r *Hosts) Get(id string) (*models.HostDecrypted, error) {
query := r.ACL(r.db)
var host models.Host
ret := query.Joins("Key").Joins("AltKey").Where("hosts.id = ?", id).First(&host)
ret := r.db.Joins("Key").Joins("AltKey").Where("hosts.id = ?", id).First(&host)
if ret.Error != nil {
return nil, ret.Error
}
@@ -48,13 +52,12 @@ func (r *Hosts) Get(id string) (*models.HostDecrypted, error) {
func (r *Hosts) Exists(id string) (bool, error) {
var count int64
ret := r.ACL(r.db.Model(&models.Host{}).Where("id = ?", id)).Count(&count)
ret := r.db.Model(&models.Host{}).Where("id = ?", id).Count(&count)
return count > 0, ret.Error
}
func (r *Hosts) Delete(id string) error {
query := r.ACL(r.db)
return query.Delete(&models.Host{Model: models.Model{ID: id}}).Error
return r.db.Delete(&models.Host{Model: models.Model{ID: id}}).Error
}
func (r *Hosts) Create(item *models.Host) error {
@@ -62,15 +65,5 @@ func (r *Hosts) Create(item *models.Host) error {
}
func (r *Hosts) Update(id string, item *models.Host) error {
query := r.ACL(r.db.Where("id = ?", id))
return query.Updates(item).Error
}
func (r *Hosts) ACL(query *gorm.DB) *gorm.DB {
if r.User.IsAdmin {
return query
}
return query.Where("hosts.owner_id = ?", r.User.ID)
return r.db.Where("id = ?", id).Updates(item).Error
}
+26 -8
View File
@@ -1,6 +1,7 @@
package hosts
import (
"errors"
"fmt"
"net/http"
@@ -19,10 +20,15 @@ func Router(app fiber.Router) {
}
func getAll(c *fiber.Ctx) error {
teamId := c.Query("teamId")
user := utils.GetUser(c)
repo := NewRepository(&Hosts{User: user})
rows, err := repo.GetAll()
if teamId != "" && !user.IsInTeam(&teamId) {
return utils.ResponseError(c, errors.New("no access"), 403)
}
rows, err := repo.GetAll(GetAllOpt{TeamID: teamId})
if err != nil {
return utils.ResponseError(c, err, 500)
}
@@ -41,8 +47,13 @@ func create(c *fiber.Ctx) error {
user := utils.GetUser(c)
repo := NewRepository(&Hosts{User: user})
if body.TeamID != nil && !user.TeamCanWrite(body.TeamID) {
return utils.ResponseError(c, errors.New("no access"), 403)
}
item := &models.Host{
OwnerID: user.ID,
OwnerID: &user.ID,
TeamID: body.TeamID,
Type: body.Type,
Label: body.Label,
Host: body.Host,
@@ -76,13 +87,17 @@ func update(c *fiber.Ctx) error {
repo := NewRepository(&Hosts{User: user})
id := c.Params("id")
exist, _ := repo.Exists(id)
if !exist {
return utils.ResponseError(c, fmt.Errorf("host %s not found", id), 404)
data, _ := repo.Get(id)
if data == nil {
return utils.ResponseError(c, errors.New("host not found"), 404)
}
if !data.CanWrite(&user.User) || !user.TeamCanWrite(body.TeamID) {
return utils.ResponseError(c, errors.New("no access"), 403)
}
item := &models.Host{
Model: models.Model{ID: id},
TeamID: body.TeamID,
Type: body.Type,
Label: body.Label,
Host: body.Host,
@@ -111,9 +126,12 @@ func delete(c *fiber.Ctx) error {
repo := NewRepository(&Hosts{User: user})
id := c.Params("id")
exist, _ := repo.Exists(id)
if !exist {
return utils.ResponseError(c, fmt.Errorf("host %s not found", id), 404)
host, _ := repo.Get(id)
if host == nil {
return utils.ResponseError(c, errors.New("host not found"), 404)
}
if !host.CanWrite(&user.User) {
return utils.ResponseError(c, errors.New("no access"), 403)
}
if err := repo.Delete(id); err != nil {
+5
View File
@@ -9,7 +9,12 @@ type CreateHostSchema struct {
Port int `json:"port"`
Metadata datatypes.JSONMap `json:"metadata"`
TeamID *string `json:"teamId"`
ParentID *string `json:"parentId"`
KeyID *string `json:"keyId"`
AltKeyID *string `json:"altKeyId"`
}
type GetAllOpt struct {
TeamID string
}
+12 -18
View File
@@ -20,10 +20,16 @@ func NewRepository(r *Keychains) *Keychains {
return r
}
func (r *Keychains) GetAll() ([]*models.Keychain, error) {
var rows []*models.Keychain
query := r.ACL(r.db.Order("created_at DESC"))
func (r *Keychains) GetAll(opt GetAllOpt) ([]*models.Keychain, error) {
query := r.db.Order("created_at DESC")
if opt.TeamID != "" {
query = query.Where("keychains.team_id = ?", opt.TeamID)
} else {
query = query.Where("keychains.owner_id = ? AND keychains.team_id IS NULL", r.User.ID)
}
var rows []*models.Keychain
ret := query.Find(&rows)
return rows, ret.Error
}
@@ -34,9 +40,7 @@ func (r *Keychains) Create(item *models.Keychain) error {
func (r *Keychains) Get(id string) (*models.Keychain, error) {
var keychain models.Keychain
query := r.ACL(r.db.Where("id = ?", id))
if err := query.First(&keychain).Error; err != nil {
if err := r.db.Where("id = ?", id).First(&keychain).Error; err != nil {
return nil, err
}
@@ -45,8 +49,7 @@ func (r *Keychains) Get(id string) (*models.Keychain, error) {
func (r *Keychains) Exists(id string) (bool, error) {
var count int64
query := r.ACL(r.db.Model(&models.Keychain{}).Where("id = ?", id))
ret := query.Count(&count)
ret := r.db.Model(&models.Keychain{}).Where("id = ?", id).Count(&count)
return count > 0, ret.Error
}
@@ -70,14 +73,5 @@ func (r *Keychains) GetDecrypted(id string) (*KeychainDecrypted, error) {
}
func (r *Keychains) Update(id string, item *models.Keychain) error {
query := r.ACL(r.db.Where("id = ?", id))
return query.Updates(item).Error
}
func (r *Keychains) ACL(query *gorm.DB) *gorm.DB {
if r.User.IsAdmin {
return query
}
return query.Where("keychains.owner_id = ?", r.User.ID)
return r.db.Where("id = ?", id).Updates(item).Error
}
+23 -10
View File
@@ -1,7 +1,7 @@
package keychains
import (
"fmt"
"errors"
"net/http"
"github.com/gofiber/fiber/v2"
@@ -23,17 +23,22 @@ type GetAllResult struct {
}
func getAll(c *fiber.Ctx) error {
teamId := c.Query("teamId")
withData := c.Query("withData")
user := utils.GetUser(c)
repo := NewRepository(&Keychains{User: user})
rows, err := repo.GetAll()
if teamId != "" && !user.IsInTeam(&teamId) {
return utils.ResponseError(c, errors.New("no access"), 403)
}
rows, err := repo.GetAll(GetAllOpt{TeamID: teamId})
if err != nil {
return utils.ResponseError(c, err, 500)
}
if withData != "true" {
if withData != "true" || (teamId != "" && !user.TeamCanWrite(&teamId)) {
return c.JSON(fiber.Map{"rows": rows})
}
@@ -67,8 +72,13 @@ func create(c *fiber.Ctx) error {
user := utils.GetUser(c)
repo := NewRepository(&Keychains{User: user})
if body.TeamID != nil && !user.TeamCanWrite(body.TeamID) {
return utils.ResponseError(c, errors.New("no access"), 403)
}
item := &models.Keychain{
OwnerID: user.ID,
OwnerID: &user.ID,
TeamID: body.TeamID,
Type: body.Type,
Label: body.Label,
}
@@ -94,15 +104,18 @@ func update(c *fiber.Ctx) error {
repo := NewRepository(&Keychains{User: user})
id := c.Params("id")
exist, _ := repo.Exists(id)
if !exist {
return utils.ResponseError(c, fmt.Errorf("key %s not found", id), 404)
data, _ := repo.Get(id)
if data == nil {
return utils.ResponseError(c, errors.New("key not found"), 404)
}
if !data.CanWrite(&user.User) || !user.TeamCanWrite(body.TeamID) {
return utils.ResponseError(c, errors.New("no access"), 403)
}
item := &models.Keychain{
Type: body.Type,
Label: body.Label,
TeamID: body.TeamID,
Type: body.Type,
Label: body.Label,
}
if err := item.EncryptData(body.Data); err != nil {
+8 -3
View File
@@ -1,7 +1,12 @@
package keychains
type CreateKeychainSchema struct {
Type string `json:"type"`
Label string `json:"label"`
Data interface{} `json:"data"`
TeamID *string `json:"teamId"`
Type string `json:"type"`
Label string `json:"label"`
Data interface{} `json:"data"`
}
type GetAllOpt struct {
TeamID string
}
+50
View File
@@ -0,0 +1,50 @@
package teams
import (
"gorm.io/gorm"
"rul.sh/vaulterm/db"
"rul.sh/vaulterm/models"
"rul.sh/vaulterm/utils"
)
type Teams struct {
db *gorm.DB
User *utils.UserContext
}
func NewRepository(r *Teams) *Teams {
if r == nil {
r = &Teams{}
}
r.db = db.Get()
return r
}
func (r *Teams) GetAll() ([]*models.Team, error) {
var rows []*models.Team
ret := r.db.Order("created_at DESC").Find(&rows)
return rows, ret.Error
}
func (r *Teams) Create(data *models.Team) error {
return r.db.Create(data).Error
}
func (r *Teams) Get(id string) (*models.Team, error) {
var data models.Team
if err := r.db.Where("id = ?", id).First(&data).Error; err != nil {
return nil, err
}
return &data, nil
}
func (r *Teams) Exists(id string) (bool, error) {
var count int64
ret := r.db.Model(&models.Team{}).Where("id = ?", id).Count(&count)
return count > 0, ret.Error
}
func (r *Teams) Update(id string, item *models.Team) error {
return r.db.Where("id = ?", id).Updates(item).Error
}
+2 -2
View File
@@ -17,8 +17,8 @@ func HandleTerm(c *websocket.Conn) {
hostRepo := hosts.NewRepository(&hosts.Hosts{User: user})
data, err := hostRepo.Get(hostId)
if data == nil {
log.Printf("Cannot find host! Error: %s\n", err.Error())
if data == nil || !data.HasAccess(&user.User) {
log.Printf("Cannot find host! %v\n", err)
c.WriteMessage(websocket.TextMessage, []byte("Host not found"))
return
}