sftpgo-mirror/dataprovider/group.go
Nicola Murino ecd488a840
data provider: remove prefer_database_credentials
Google Cloud Storage credentials are now always stored within the data
provider.

Added a migration to read credentials from disk and store them inside the
data provider.

After v2.3 we can also remove credentials_path

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-04-28 12:55:01 +02:00

220 lines
6.6 KiB
Go

package dataprovider
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"github.com/sftpgo/sdk"
"github.com/drakkan/sftpgo/v2/logger"
"github.com/drakkan/sftpgo/v2/plugin"
"github.com/drakkan/sftpgo/v2/util"
"github.com/drakkan/sftpgo/v2/vfs"
)
// GroupUserSettings defines the settings to apply to users
type GroupUserSettings struct {
sdk.BaseGroupUserSettings
// Filesystem configuration details
FsConfig vfs.Filesystem `json:"filesystem"`
}
// Group defines an SFTPGo group.
// Groups are used to easily configure similar users
type Group struct {
sdk.BaseGroup
// settings to apply to users for whom this is a primary group
UserSettings GroupUserSettings `json:"user_settings,omitempty"`
// Mapping between virtual paths and virtual folders
VirtualFolders []vfs.VirtualFolder `json:"virtual_folders,omitempty"`
}
// GetPermissions returns the permissions as list
func (g *Group) GetPermissions() []sdk.DirectoryPermissions {
result := make([]sdk.DirectoryPermissions, 0, len(g.UserSettings.Permissions))
for k, v := range g.UserSettings.Permissions {
result = append(result, sdk.DirectoryPermissions{
Path: k,
Permissions: v,
})
}
return result
}
// GetAllowedIPAsString returns the allowed IP as comma separated string
func (g *Group) GetAllowedIPAsString() string {
return strings.Join(g.UserSettings.Filters.AllowedIP, ",")
}
// GetDeniedIPAsString returns the denied IP as comma separated string
func (g *Group) GetDeniedIPAsString() string {
return strings.Join(g.UserSettings.Filters.DeniedIP, ",")
}
// HasExternalAuth returns true if the external authentication is globally enabled
// and it is not disabled for this group
func (g *Group) HasExternalAuth() bool {
if g.UserSettings.Filters.Hooks.ExternalAuthDisabled {
return false
}
if config.ExternalAuthHook != "" {
return true
}
return plugin.Handler.HasAuthenticators()
}
// SetEmptySecretsIfNil sets the secrets to empty if nil
func (g *Group) SetEmptySecretsIfNil() {
g.UserSettings.FsConfig.SetEmptySecretsIfNil()
for idx := range g.VirtualFolders {
vfolder := &g.VirtualFolders[idx]
vfolder.FsConfig.SetEmptySecretsIfNil()
}
}
// PrepareForRendering prepares a group for rendering.
// It hides confidential data and set to nil the empty secrets
// so they are not serialized
func (g *Group) PrepareForRendering() {
g.UserSettings.FsConfig.HideConfidentialData()
g.UserSettings.FsConfig.SetNilSecretsIfEmpty()
for idx := range g.VirtualFolders {
folder := &g.VirtualFolders[idx]
folder.PrepareForRendering()
}
}
// RenderAsJSON implements the renderer interface used within plugins
func (g *Group) RenderAsJSON(reload bool) ([]byte, error) {
if reload {
group, err := provider.groupExists(g.Name)
if err != nil {
providerLog(logger.LevelError, "unable to reload group before rendering as json: %v", err)
return nil, err
}
group.PrepareForRendering()
return json.Marshal(group)
}
g.PrepareForRendering()
return json.Marshal(g)
}
// GetEncryptionAdditionalData returns the additional data to use for AEAD
func (g *Group) GetEncryptionAdditionalData() string {
return fmt.Sprintf("group_%v", g.Name)
}
// HasRedactedSecret returns true if the user has a redacted secret
func (g *Group) hasRedactedSecret() bool {
for idx := range g.VirtualFolders {
folder := &g.VirtualFolders[idx]
if folder.HasRedactedSecret() {
return true
}
}
return g.UserSettings.FsConfig.HasRedactedSecret()
}
func (g *Group) validate() error {
g.SetEmptySecretsIfNil()
if g.Name == "" {
return util.NewValidationError("name is mandatory")
}
if config.NamingRules&1 == 0 && !usernameRegex.MatchString(g.Name) {
return util.NewValidationError(fmt.Sprintf("name %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~", g.Name))
}
if g.hasRedactedSecret() {
return util.NewValidationError("cannot save a user with a redacted secret")
}
vfolders, err := validateAssociatedVirtualFolders(g.VirtualFolders)
if err != nil {
return err
}
g.VirtualFolders = vfolders
return g.validateUserSettings()
}
func (g *Group) validateUserSettings() error {
if g.UserSettings.HomeDir != "" {
g.UserSettings.HomeDir = filepath.Clean(g.UserSettings.HomeDir)
if !filepath.IsAbs(g.UserSettings.HomeDir) {
return util.NewValidationError(fmt.Sprintf("home_dir must be an absolute path, actual value: %v",
g.UserSettings.HomeDir))
}
}
if err := g.UserSettings.FsConfig.Validate(g.GetEncryptionAdditionalData()); err != nil {
return err
}
if g.UserSettings.TotalDataTransfer > 0 {
// if a total data transfer is defined we reset the separate upload and download limits
g.UserSettings.UploadDataTransfer = 0
g.UserSettings.DownloadDataTransfer = 0
}
if len(g.UserSettings.Permissions) > 0 {
permissions, err := validateUserPermissions(g.UserSettings.Permissions)
if err != nil {
return err
}
g.UserSettings.Permissions = permissions
}
if err := validateBaseFilters(&g.UserSettings.Filters); err != nil {
return err
}
if !g.HasExternalAuth() {
g.UserSettings.Filters.ExternalAuthCacheTime = 0
}
g.UserSettings.Filters.UserType = ""
return nil
}
func (g *Group) getACopy() Group {
users := make([]string, len(g.Users))
copy(users, g.Users)
virtualFolders := make([]vfs.VirtualFolder, 0, len(g.VirtualFolders))
for idx := range g.VirtualFolders {
vfolder := g.VirtualFolders[idx].GetACopy()
virtualFolders = append(virtualFolders, vfolder)
}
permissions := make(map[string][]string)
for k, v := range g.UserSettings.Permissions {
perms := make([]string, len(v))
copy(perms, v)
permissions[k] = perms
}
return Group{
BaseGroup: sdk.BaseGroup{
ID: g.ID,
Name: g.Name,
Description: g.Description,
CreatedAt: g.CreatedAt,
UpdatedAt: g.UpdatedAt,
Users: users,
},
UserSettings: GroupUserSettings{
BaseGroupUserSettings: sdk.BaseGroupUserSettings{
HomeDir: g.UserSettings.HomeDir,
MaxSessions: g.UserSettings.MaxSessions,
QuotaSize: g.UserSettings.QuotaSize,
QuotaFiles: g.UserSettings.QuotaFiles,
Permissions: permissions,
UploadBandwidth: g.UserSettings.UploadBandwidth,
DownloadBandwidth: g.UserSettings.DownloadBandwidth,
UploadDataTransfer: g.UserSettings.UploadDataTransfer,
DownloadDataTransfer: g.UserSettings.DownloadDataTransfer,
TotalDataTransfer: g.UserSettings.TotalDataTransfer,
Filters: copyBaseUserFilters(g.UserSettings.Filters),
},
FsConfig: g.UserSettings.FsConfig.GetACopy(),
},
VirtualFolders: virtualFolders,
}
}
// GetUsersAsString returns the list of users as comma separated string
func (g *Group) GetUsersAsString() string {
return strings.Join(g.Users, ",")
}