1package dataprovider
2
3import (
4	"crypto/sha256"
5	"encoding/base64"
6	"encoding/json"
7	"errors"
8	"fmt"
9	"math"
10	"net"
11	"os"
12	"path"
13	"path/filepath"
14	"strconv"
15	"strings"
16	"time"
17
18	"github.com/drakkan/sftpgo/v2/kms"
19	"github.com/drakkan/sftpgo/v2/logger"
20	"github.com/drakkan/sftpgo/v2/mfa"
21	"github.com/drakkan/sftpgo/v2/sdk"
22	"github.com/drakkan/sftpgo/v2/util"
23	"github.com/drakkan/sftpgo/v2/vfs"
24)
25
26// Available permissions for SFTPGo users
27const (
28	// All permissions are granted
29	PermAny = "*"
30	// List items such as files and directories is allowed
31	PermListItems = "list"
32	// download files is allowed
33	PermDownload = "download"
34	// upload files is allowed
35	PermUpload = "upload"
36	// overwrite an existing file, while uploading, is allowed
37	// upload permission is required to allow file overwrite
38	PermOverwrite = "overwrite"
39	// delete files or directories is allowed
40	PermDelete = "delete"
41	// delete files is allowed
42	PermDeleteFiles = "delete_files"
43	// delete directories is allowed
44	PermDeleteDirs = "delete_dirs"
45	// rename files or directories is allowed
46	PermRename = "rename"
47	// rename files is allowed
48	PermRenameFiles = "rename_files"
49	// rename directories is allowed
50	PermRenameDirs = "rename_dirs"
51	// create directories is allowed
52	PermCreateDirs = "create_dirs"
53	// create symbolic links is allowed
54	PermCreateSymlinks = "create_symlinks"
55	// changing file or directory permissions is allowed
56	PermChmod = "chmod"
57	// changing file or directory owner and group is allowed
58	PermChown = "chown"
59	// changing file or directory access and modification time is allowed
60	PermChtimes = "chtimes"
61)
62
63// Available login methods
64const (
65	LoginMethodNoAuthTryed            = "no_auth_tryed"
66	LoginMethodPassword               = "password"
67	SSHLoginMethodPublicKey           = "publickey"
68	SSHLoginMethodKeyboardInteractive = "keyboard-interactive"
69	SSHLoginMethodKeyAndPassword      = "publickey+password"
70	SSHLoginMethodKeyAndKeyboardInt   = "publickey+keyboard-interactive"
71	LoginMethodTLSCertificate         = "TLSCertificate"
72	LoginMethodTLSCertificateAndPwd   = "TLSCertificate+password"
73)
74
75var (
76	errNoMatchingVirtualFolder = errors.New("no matching virtual folder found")
77	permsRenameAny             = []string{PermRename, PermRenameDirs, PermRenameFiles}
78	permsDeleteAny             = []string{PermDelete, PermDeleteDirs, PermDeleteFiles}
79	permsCreateAny             = []string{PermUpload, PermCreateDirs}
80)
81
82// User defines a SFTPGo user
83type User struct {
84	sdk.BaseUser
85	// Mapping between virtual paths and virtual folders
86	VirtualFolders []vfs.VirtualFolder `json:"virtual_folders,omitempty"`
87	// Filesystem configuration details
88	FsConfig vfs.Filesystem `json:"filesystem"`
89	// we store the filesystem here using the base path as key.
90	fsCache map[string]vfs.Fs `json:"-"`
91}
92
93// GetFilesystem returns the base filesystem for this user
94func (u *User) GetFilesystem(connectionID string) (fs vfs.Fs, err error) {
95	fs, err = u.getRootFs(connectionID)
96	if err != nil {
97		return fs, err
98	}
99	u.fsCache = make(map[string]vfs.Fs)
100	u.fsCache["/"] = fs
101	return fs, err
102}
103
104func (u *User) getRootFs(connectionID string) (fs vfs.Fs, err error) {
105	switch u.FsConfig.Provider {
106	case sdk.S3FilesystemProvider:
107		return vfs.NewS3Fs(connectionID, u.GetHomeDir(), "", u.FsConfig.S3Config)
108	case sdk.GCSFilesystemProvider:
109		config := u.FsConfig.GCSConfig
110		config.CredentialFile = u.GetGCSCredentialsFilePath()
111		return vfs.NewGCSFs(connectionID, u.GetHomeDir(), "", config)
112	case sdk.AzureBlobFilesystemProvider:
113		return vfs.NewAzBlobFs(connectionID, u.GetHomeDir(), "", u.FsConfig.AzBlobConfig)
114	case sdk.CryptedFilesystemProvider:
115		return vfs.NewCryptFs(connectionID, u.GetHomeDir(), "", u.FsConfig.CryptConfig)
116	case sdk.SFTPFilesystemProvider:
117		forbiddenSelfUsers, err := u.getForbiddenSFTPSelfUsers(u.FsConfig.SFTPConfig.Username)
118		if err != nil {
119			return nil, err
120		}
121		forbiddenSelfUsers = append(forbiddenSelfUsers, u.Username)
122		return vfs.NewSFTPFs(connectionID, "", u.GetHomeDir(), forbiddenSelfUsers, u.FsConfig.SFTPConfig)
123	default:
124		return vfs.NewOsFs(connectionID, u.GetHomeDir(), ""), nil
125	}
126}
127
128// CheckFsRoot check the root directory for the main fs and the virtual folders.
129// It returns an error if the main filesystem cannot be created
130func (u *User) CheckFsRoot(connectionID string) error {
131	if u.Filters.DisableFsChecks {
132		return nil
133	}
134	fs, err := u.GetFilesystemForPath("/", connectionID)
135	if err != nil {
136		logger.Warn(logSender, connectionID, "could not create main filesystem for user %#v err: %v", u.Username, err)
137		return err
138	}
139	fs.CheckRootPath(u.Username, u.GetUID(), u.GetGID())
140	for idx := range u.VirtualFolders {
141		v := &u.VirtualFolders[idx]
142		fs, err = u.GetFilesystemForPath(v.VirtualPath, connectionID)
143		if err == nil {
144			fs.CheckRootPath(u.Username, u.GetUID(), u.GetGID())
145		}
146		// now check intermediary folders
147		fs, err = u.GetFilesystemForPath(path.Dir(v.VirtualPath), connectionID)
148		if err == nil && !fs.HasVirtualFolders() {
149			fsPath, err := fs.ResolvePath(v.VirtualPath)
150			if err != nil {
151				continue
152			}
153			err = fs.MkdirAll(fsPath, u.GetUID(), u.GetGID())
154			logger.Debug(logSender, connectionID, "create intermediary dir to %#v, path %#v, err: %v",
155				v.VirtualPath, fsPath, err)
156		}
157	}
158	return nil
159}
160
161// isFsEqual returns true if the fs has the same configuration
162func (u *User) isFsEqual(other *User) bool {
163	if u.FsConfig.Provider == sdk.LocalFilesystemProvider && u.GetHomeDir() != other.GetHomeDir() {
164		return false
165	}
166	if !u.FsConfig.IsEqual(&other.FsConfig) {
167		return false
168	}
169	if len(u.VirtualFolders) != len(other.VirtualFolders) {
170		return false
171	}
172	for idx := range u.VirtualFolders {
173		f := &u.VirtualFolders[idx]
174		found := false
175		for idx1 := range other.VirtualFolders {
176			f1 := &other.VirtualFolders[idx1]
177			if f.VirtualPath == f1.VirtualPath {
178				found = true
179				if f.FsConfig.Provider == sdk.LocalFilesystemProvider && f.MappedPath != f1.MappedPath {
180					return false
181				}
182				if !f.FsConfig.IsEqual(&f1.FsConfig) {
183					return false
184				}
185			}
186		}
187		if !found {
188			return false
189		}
190	}
191	return true
192}
193
194// CheckLoginConditions checks if the user is active and not expired
195func (u *User) CheckLoginConditions() error {
196	if u.Status < 1 {
197		return fmt.Errorf("user %#v is disabled", u.Username)
198	}
199	if u.ExpirationDate > 0 && u.ExpirationDate < util.GetTimeAsMsSinceEpoch(time.Now()) {
200		return fmt.Errorf("user %#v is expired, expiration timestamp: %v current timestamp: %v", u.Username,
201			u.ExpirationDate, util.GetTimeAsMsSinceEpoch(time.Now()))
202	}
203	return nil
204}
205
206// hideConfidentialData hides user confidential data
207func (u *User) hideConfidentialData() {
208	u.Password = ""
209	u.FsConfig.HideConfidentialData()
210	if u.Filters.TOTPConfig.Secret != nil {
211		u.Filters.TOTPConfig.Secret.Hide()
212	}
213	for _, code := range u.Filters.RecoveryCodes {
214		if code.Secret != nil {
215			code.Secret.Hide()
216		}
217	}
218}
219
220// GetSubDirPermissions returns permissions for sub directories
221func (u *User) GetSubDirPermissions() []sdk.DirectoryPermissions {
222	var result []sdk.DirectoryPermissions
223	for k, v := range u.Permissions {
224		if k == "/" {
225			continue
226		}
227		dirPerms := sdk.DirectoryPermissions{
228			Path:        k,
229			Permissions: v,
230		}
231		result = append(result, dirPerms)
232	}
233	return result
234}
235
236// RenderAsJSON implements the renderer interface used within plugins
237func (u *User) RenderAsJSON(reload bool) ([]byte, error) {
238	if reload {
239		user, err := provider.userExists(u.Username)
240		if err != nil {
241			providerLog(logger.LevelWarn, "unable to reload user before rendering as json: %v", err)
242			return nil, err
243		}
244		user.PrepareForRendering()
245		return json.Marshal(user)
246	}
247	u.PrepareForRendering()
248	return json.Marshal(u)
249}
250
251// PrepareForRendering prepares a user for rendering.
252// It hides confidential data and set to nil the empty secrets
253// so they are not serialized
254func (u *User) PrepareForRendering() {
255	u.hideConfidentialData()
256	u.FsConfig.SetNilSecretsIfEmpty()
257	for idx := range u.VirtualFolders {
258		folder := &u.VirtualFolders[idx]
259		folder.PrepareForRendering()
260	}
261}
262
263// HasRedactedSecret returns true if the user has a redacted secret
264func (u *User) hasRedactedSecret() bool {
265	if u.FsConfig.HasRedactedSecret() {
266		return true
267	}
268
269	for idx := range u.VirtualFolders {
270		folder := &u.VirtualFolders[idx]
271		if folder.HasRedactedSecret() {
272			return true
273		}
274	}
275
276	return u.Filters.TOTPConfig.Secret.IsRedacted()
277}
278
279// CloseFs closes the underlying filesystems
280func (u *User) CloseFs() error {
281	if u.fsCache == nil {
282		return nil
283	}
284
285	var err error
286	for _, fs := range u.fsCache {
287		errClose := fs.Close()
288		if err == nil {
289			err = errClose
290		}
291	}
292	return err
293}
294
295// IsPasswordHashed returns true if the password is hashed
296func (u *User) IsPasswordHashed() bool {
297	return util.IsStringPrefixInSlice(u.Password, hashPwdPrefixes)
298}
299
300// IsTLSUsernameVerificationEnabled returns true if we need to extract the username
301// from the client TLS certificate
302func (u *User) IsTLSUsernameVerificationEnabled() bool {
303	if u.Filters.TLSUsername != "" {
304		return u.Filters.TLSUsername != sdk.TLSUsernameNone
305	}
306	return false
307}
308
309// SetEmptySecrets sets to empty any user secret
310func (u *User) SetEmptySecrets() {
311	u.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
312	u.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
313	u.FsConfig.AzBlobConfig.AccountKey = kms.NewEmptySecret()
314	u.FsConfig.AzBlobConfig.SASURL = kms.NewEmptySecret()
315	u.FsConfig.CryptConfig.Passphrase = kms.NewEmptySecret()
316	u.FsConfig.SFTPConfig.Password = kms.NewEmptySecret()
317	u.FsConfig.SFTPConfig.PrivateKey = kms.NewEmptySecret()
318	for idx := range u.VirtualFolders {
319		folder := &u.VirtualFolders[idx]
320		folder.FsConfig.SetEmptySecretsIfNil()
321	}
322	u.Filters.TOTPConfig.Secret = kms.NewEmptySecret()
323}
324
325// GetPermissionsForPath returns the permissions for the given path.
326// The path must be a SFTPGo exposed path
327func (u *User) GetPermissionsForPath(p string) []string {
328	permissions := []string{}
329	if perms, ok := u.Permissions["/"]; ok {
330		// if only root permissions are defined returns them unconditionally
331		if len(u.Permissions) == 1 {
332			return perms
333		}
334		// fallback permissions
335		permissions = perms
336	}
337	dirsForPath := util.GetDirsForVirtualPath(p)
338	// dirsForPath contains all the dirs for a given path in reverse order
339	// for example if the path is: /1/2/3/4 it contains:
340	// [ "/1/2/3/4", "/1/2/3", "/1/2", "/1", "/" ]
341	// so the first match is the one we are interested to
342	for idx := range dirsForPath {
343		if perms, ok := u.Permissions[dirsForPath[idx]]; ok {
344			permissions = perms
345			break
346		}
347	}
348	return permissions
349}
350
351func (u *User) getForbiddenSFTPSelfUsers(username string) ([]string, error) {
352	sftpUser, err := UserExists(username)
353	if err == nil {
354		// we don't allow local nested SFTP folders
355		var forbiddens []string
356		if sftpUser.FsConfig.Provider == sdk.SFTPFilesystemProvider {
357			forbiddens = append(forbiddens, sftpUser.Username)
358			return forbiddens, nil
359		}
360		for idx := range sftpUser.VirtualFolders {
361			v := &sftpUser.VirtualFolders[idx]
362			if v.FsConfig.Provider == sdk.SFTPFilesystemProvider {
363				forbiddens = append(forbiddens, sftpUser.Username)
364				return forbiddens, nil
365			}
366		}
367		return forbiddens, nil
368	}
369	if _, ok := err.(*util.RecordNotFoundError); !ok {
370		return nil, err
371	}
372
373	return nil, nil
374}
375
376// GetFsConfigForPath returns the file system configuration for the specified virtual path
377func (u *User) GetFsConfigForPath(virtualPath string) vfs.Filesystem {
378	if virtualPath != "" && virtualPath != "/" && len(u.VirtualFolders) > 0 {
379		folder, err := u.GetVirtualFolderForPath(virtualPath)
380		if err == nil {
381			return folder.FsConfig
382		}
383	}
384
385	return u.FsConfig
386}
387
388// GetFilesystemForPath returns the filesystem for the given path
389func (u *User) GetFilesystemForPath(virtualPath, connectionID string) (vfs.Fs, error) {
390	if u.fsCache == nil {
391		u.fsCache = make(map[string]vfs.Fs)
392	}
393	if virtualPath != "" && virtualPath != "/" && len(u.VirtualFolders) > 0 {
394		folder, err := u.GetVirtualFolderForPath(virtualPath)
395		if err == nil {
396			if fs, ok := u.fsCache[folder.VirtualPath]; ok {
397				return fs, nil
398			}
399			forbiddenSelfUsers := []string{u.Username}
400			if folder.FsConfig.Provider == sdk.SFTPFilesystemProvider {
401				forbiddens, err := u.getForbiddenSFTPSelfUsers(folder.FsConfig.SFTPConfig.Username)
402				if err != nil {
403					return nil, err
404				}
405				forbiddenSelfUsers = append(forbiddenSelfUsers, forbiddens...)
406			}
407			fs, err := folder.GetFilesystem(connectionID, forbiddenSelfUsers)
408			if err == nil {
409				u.fsCache[folder.VirtualPath] = fs
410			}
411			return fs, err
412		}
413	}
414
415	if val, ok := u.fsCache["/"]; ok {
416		return val, nil
417	}
418
419	return u.GetFilesystem(connectionID)
420}
421
422// GetVirtualFolderForPath returns the virtual folder containing the specified virtual path.
423// If the path is not inside a virtual folder an error is returned
424func (u *User) GetVirtualFolderForPath(virtualPath string) (vfs.VirtualFolder, error) {
425	var folder vfs.VirtualFolder
426	if len(u.VirtualFolders) == 0 {
427		return folder, errNoMatchingVirtualFolder
428	}
429	dirsForPath := util.GetDirsForVirtualPath(virtualPath)
430	for index := range dirsForPath {
431		for idx := range u.VirtualFolders {
432			v := &u.VirtualFolders[idx]
433			if v.VirtualPath == dirsForPath[index] {
434				return *v, nil
435			}
436		}
437	}
438	return folder, errNoMatchingVirtualFolder
439}
440
441// ScanQuota scans the user home dir and virtual folders, included in its quota,
442// and returns the number of files and their size
443func (u *User) ScanQuota() (int, int64, error) {
444	fs, err := u.getRootFs("")
445	if err != nil {
446		return 0, 0, err
447	}
448	defer fs.Close()
449	numFiles, size, err := fs.ScanRootDirContents()
450	if err != nil {
451		return numFiles, size, err
452	}
453	for idx := range u.VirtualFolders {
454		v := &u.VirtualFolders[idx]
455		if !v.IsIncludedInUserQuota() {
456			continue
457		}
458		num, s, err := v.ScanQuota()
459		if err != nil {
460			return numFiles, size, err
461		}
462		numFiles += num
463		size += s
464	}
465
466	return numFiles, size, nil
467}
468
469// GetVirtualFoldersInPath returns the virtual folders inside virtualPath including
470// any parents
471func (u *User) GetVirtualFoldersInPath(virtualPath string) map[string]bool {
472	result := make(map[string]bool)
473
474	for idx := range u.VirtualFolders {
475		v := &u.VirtualFolders[idx]
476		dirsForPath := util.GetDirsForVirtualPath(v.VirtualPath)
477		for index := range dirsForPath {
478			d := dirsForPath[index]
479			if d == "/" {
480				continue
481			}
482			if path.Dir(d) == virtualPath {
483				result[d] = true
484			}
485		}
486	}
487
488	return result
489}
490
491// AddVirtualDirs adds virtual folders, if defined, to the given files list
492func (u *User) AddVirtualDirs(list []os.FileInfo, virtualPath string) []os.FileInfo {
493	if len(u.VirtualFolders) == 0 {
494		return list
495	}
496
497	for dir := range u.GetVirtualFoldersInPath(virtualPath) {
498		fi := vfs.NewFileInfo(dir, true, 0, time.Now(), false)
499		found := false
500		for index := range list {
501			if list[index].Name() == fi.Name() {
502				list[index] = fi
503				found = true
504				break
505			}
506		}
507		if !found {
508			list = append(list, fi)
509		}
510	}
511	return list
512}
513
514// IsMappedPath returns true if the specified filesystem path has a virtual folder mapping.
515// The filesystem path must be cleaned before calling this method
516func (u *User) IsMappedPath(fsPath string) bool {
517	for idx := range u.VirtualFolders {
518		v := &u.VirtualFolders[idx]
519		if fsPath == v.MappedPath {
520			return true
521		}
522	}
523	return false
524}
525
526// IsVirtualFolder returns true if the specified virtual path is a virtual folder
527func (u *User) IsVirtualFolder(virtualPath string) bool {
528	for idx := range u.VirtualFolders {
529		v := &u.VirtualFolders[idx]
530		if virtualPath == v.VirtualPath {
531			return true
532		}
533	}
534	return false
535}
536
537// HasVirtualFoldersInside returns true if there are virtual folders inside the
538// specified virtual path. We assume that path are cleaned
539func (u *User) HasVirtualFoldersInside(virtualPath string) bool {
540	if virtualPath == "/" && len(u.VirtualFolders) > 0 {
541		return true
542	}
543	for idx := range u.VirtualFolders {
544		v := &u.VirtualFolders[idx]
545		if len(v.VirtualPath) > len(virtualPath) {
546			if strings.HasPrefix(v.VirtualPath, virtualPath+"/") {
547				return true
548			}
549		}
550	}
551	return false
552}
553
554// HasPermissionsInside returns true if the specified virtualPath has no permissions itself and
555// no subdirs with defined permissions
556func (u *User) HasPermissionsInside(virtualPath string) bool {
557	for dir := range u.Permissions {
558		if dir == virtualPath {
559			return true
560		} else if len(dir) > len(virtualPath) {
561			if strings.HasPrefix(dir, virtualPath+"/") {
562				return true
563			}
564		}
565	}
566	return false
567}
568
569// HasPerm returns true if the user has the given permission or any permission
570func (u *User) HasPerm(permission, path string) bool {
571	perms := u.GetPermissionsForPath(path)
572	if util.IsStringInSlice(PermAny, perms) {
573		return true
574	}
575	return util.IsStringInSlice(permission, perms)
576}
577
578// HasAnyPerm returns true if the user has at least one of the given permissions
579func (u *User) HasAnyPerm(permissions []string, path string) bool {
580	perms := u.GetPermissionsForPath(path)
581	if util.IsStringInSlice(PermAny, perms) {
582		return true
583	}
584	for _, permission := range permissions {
585		if util.IsStringInSlice(permission, perms) {
586			return true
587		}
588	}
589	return false
590}
591
592// HasPerms returns true if the user has all the given permissions
593func (u *User) HasPerms(permissions []string, path string) bool {
594	perms := u.GetPermissionsForPath(path)
595	if util.IsStringInSlice(PermAny, perms) {
596		return true
597	}
598	for _, permission := range permissions {
599		if !util.IsStringInSlice(permission, perms) {
600			return false
601		}
602	}
603	return true
604}
605
606// HasPermsDeleteAll returns true if the user can delete both files and directories
607// for the given path
608func (u *User) HasPermsDeleteAll(path string) bool {
609	perms := u.GetPermissionsForPath(path)
610	canDeleteFiles := false
611	canDeleteDirs := false
612	for _, permission := range perms {
613		if permission == PermAny || permission == PermDelete {
614			return true
615		}
616		if permission == PermDeleteFiles {
617			canDeleteFiles = true
618		}
619		if permission == PermDeleteDirs {
620			canDeleteDirs = true
621		}
622	}
623	return canDeleteFiles && canDeleteDirs
624}
625
626// HasPermsRenameAll returns true if the user can rename both files and directories
627// for the given path
628func (u *User) HasPermsRenameAll(path string) bool {
629	perms := u.GetPermissionsForPath(path)
630	canRenameFiles := false
631	canRenameDirs := false
632	for _, permission := range perms {
633		if permission == PermAny || permission == PermRename {
634			return true
635		}
636		if permission == PermRenameFiles {
637			canRenameFiles = true
638		}
639		if permission == PermRenameDirs {
640			canRenameDirs = true
641		}
642	}
643	return canRenameFiles && canRenameDirs
644}
645
646// HasNoQuotaRestrictions returns true if no quota restrictions need to be applyed
647func (u *User) HasNoQuotaRestrictions(checkFiles bool) bool {
648	if u.QuotaSize == 0 && (!checkFiles || u.QuotaFiles == 0) {
649		return true
650	}
651	return false
652}
653
654// IsLoginMethodAllowed returns true if the specified login method is allowed
655func (u *User) IsLoginMethodAllowed(loginMethod string, partialSuccessMethods []string) bool {
656	if len(u.Filters.DeniedLoginMethods) == 0 {
657		return true
658	}
659	if len(partialSuccessMethods) == 1 {
660		for _, method := range u.GetNextAuthMethods(partialSuccessMethods, true) {
661			if method == loginMethod {
662				return true
663			}
664		}
665	}
666	if util.IsStringInSlice(loginMethod, u.Filters.DeniedLoginMethods) {
667		return false
668	}
669	return true
670}
671
672// GetNextAuthMethods returns the list of authentications methods that
673// can continue for multi-step authentication
674func (u *User) GetNextAuthMethods(partialSuccessMethods []string, isPasswordAuthEnabled bool) []string {
675	var methods []string
676	if len(partialSuccessMethods) != 1 {
677		return methods
678	}
679	if partialSuccessMethods[0] != SSHLoginMethodPublicKey {
680		return methods
681	}
682	for _, method := range u.GetAllowedLoginMethods() {
683		if method == SSHLoginMethodKeyAndPassword && isPasswordAuthEnabled {
684			methods = append(methods, LoginMethodPassword)
685		}
686		if method == SSHLoginMethodKeyAndKeyboardInt {
687			methods = append(methods, SSHLoginMethodKeyboardInteractive)
688		}
689	}
690	return methods
691}
692
693// IsPartialAuth returns true if the specified login method is a step for
694// a multi-step Authentication.
695// We support publickey+password and publickey+keyboard-interactive, so
696// only publickey can returns partial success.
697// We can have partial success if only multi-step Auth methods are enabled
698func (u *User) IsPartialAuth(loginMethod string) bool {
699	if loginMethod != SSHLoginMethodPublicKey {
700		return false
701	}
702	for _, method := range u.GetAllowedLoginMethods() {
703		if method == LoginMethodTLSCertificate || method == LoginMethodTLSCertificateAndPwd {
704			continue
705		}
706		if !util.IsStringInSlice(method, SSHMultiStepsLoginMethods) {
707			return false
708		}
709	}
710	return true
711}
712
713// GetAllowedLoginMethods returns the allowed login methods
714func (u *User) GetAllowedLoginMethods() []string {
715	var allowedMethods []string
716	for _, method := range ValidLoginMethods {
717		if !util.IsStringInSlice(method, u.Filters.DeniedLoginMethods) {
718			allowedMethods = append(allowedMethods, method)
719		}
720	}
721	return allowedMethods
722}
723
724// GetFlatFilePatterns returns file patterns as flat list
725// duplicating a path if it has both allowed and denied patterns
726func (u *User) GetFlatFilePatterns() []sdk.PatternsFilter {
727	var result []sdk.PatternsFilter
728
729	for _, pattern := range u.Filters.FilePatterns {
730		if len(pattern.AllowedPatterns) > 0 {
731			result = append(result, sdk.PatternsFilter{
732				Path:            pattern.Path,
733				AllowedPatterns: pattern.AllowedPatterns,
734			})
735		}
736		if len(pattern.DeniedPatterns) > 0 {
737			result = append(result, sdk.PatternsFilter{
738				Path:           pattern.Path,
739				DeniedPatterns: pattern.DeniedPatterns,
740			})
741		}
742	}
743	return result
744}
745
746// IsFileAllowed returns true if the specified file is allowed by the file restrictions filters
747func (u *User) IsFileAllowed(virtualPath string) bool {
748	return u.isFilePatternAllowed(virtualPath)
749}
750
751func (u *User) isFilePatternAllowed(virtualPath string) bool {
752	if len(u.Filters.FilePatterns) == 0 {
753		return true
754	}
755	dirsForPath := util.GetDirsForVirtualPath(path.Dir(virtualPath))
756	var filter sdk.PatternsFilter
757	for _, dir := range dirsForPath {
758		for _, f := range u.Filters.FilePatterns {
759			if f.Path == dir {
760				filter = f
761				break
762			}
763		}
764		if filter.Path != "" {
765			break
766		}
767	}
768	if filter.Path != "" {
769		toMatch := strings.ToLower(path.Base(virtualPath))
770		for _, denied := range filter.DeniedPatterns {
771			matched, err := path.Match(denied, toMatch)
772			if err != nil || matched {
773				return false
774			}
775		}
776		for _, allowed := range filter.AllowedPatterns {
777			matched, err := path.Match(allowed, toMatch)
778			if err == nil && matched {
779				return true
780			}
781		}
782		return len(filter.AllowedPatterns) == 0
783	}
784	return true
785}
786
787// CanManageMFA returns true if the user can add a multi-factor authentication configuration
788func (u *User) CanManageMFA() bool {
789	if util.IsStringInSlice(sdk.WebClientMFADisabled, u.Filters.WebClient) {
790		return false
791	}
792	return len(mfa.GetAvailableTOTPConfigs()) > 0
793}
794
795// CanManageShares returns true if the user can add, update and list shares
796func (u *User) CanManageShares() bool {
797	return !util.IsStringInSlice(sdk.WebClientSharesDisabled, u.Filters.WebClient)
798}
799
800// CanResetPassword returns true if this user is allowed to reset its password
801func (u *User) CanResetPassword() bool {
802	return !util.IsStringInSlice(sdk.WebClientPasswordResetDisabled, u.Filters.WebClient)
803}
804
805// CanChangePassword returns true if this user is allowed to change its password
806func (u *User) CanChangePassword() bool {
807	return !util.IsStringInSlice(sdk.WebClientPasswordChangeDisabled, u.Filters.WebClient)
808}
809
810// CanChangeAPIKeyAuth returns true if this user is allowed to enable/disable API key authentication
811func (u *User) CanChangeAPIKeyAuth() bool {
812	return !util.IsStringInSlice(sdk.WebClientAPIKeyAuthChangeDisabled, u.Filters.WebClient)
813}
814
815// CanChangeInfo returns true if this user is allowed to change its info such as email and description
816func (u *User) CanChangeInfo() bool {
817	return !util.IsStringInSlice(sdk.WebClientInfoChangeDisabled, u.Filters.WebClient)
818}
819
820// CanManagePublicKeys returns true if this user is allowed to manage public keys
821// from the web client. Used in web client UI
822func (u *User) CanManagePublicKeys() bool {
823	return !util.IsStringInSlice(sdk.WebClientPubKeyChangeDisabled, u.Filters.WebClient)
824}
825
826// CanAddFilesFromWeb returns true if the client can add files from the web UI.
827// The specified target is the directory where the files must be uploaded
828func (u *User) CanAddFilesFromWeb(target string) bool {
829	if util.IsStringInSlice(sdk.WebClientWriteDisabled, u.Filters.WebClient) {
830		return false
831	}
832	return u.HasPerm(PermUpload, target) || u.HasPerm(PermOverwrite, target)
833}
834
835// CanAddDirsFromWeb returns true if the client can add directories from the web UI.
836// The specified target is the directory where the new directory must be created
837func (u *User) CanAddDirsFromWeb(target string) bool {
838	if util.IsStringInSlice(sdk.WebClientWriteDisabled, u.Filters.WebClient) {
839		return false
840	}
841	return u.HasPerm(PermCreateDirs, target)
842}
843
844// CanRenameFromWeb returns true if the client can rename objects from the web UI.
845// The specified src and dest are the source and target directories for the rename.
846func (u *User) CanRenameFromWeb(src, dest string) bool {
847	if util.IsStringInSlice(sdk.WebClientWriteDisabled, u.Filters.WebClient) {
848		return false
849	}
850	if u.HasAnyPerm(permsRenameAny, src) && u.HasAnyPerm(permsRenameAny, dest) {
851		return true
852	}
853	if !u.HasAnyPerm(permsDeleteAny, src) {
854		return false
855	}
856	return u.HasAnyPerm(permsCreateAny, dest)
857}
858
859// CanDeleteFromWeb returns true if the client can delete objects from the web UI.
860// The specified target is the parent directory for the object to delete
861func (u *User) CanDeleteFromWeb(target string) bool {
862	if util.IsStringInSlice(sdk.WebClientWriteDisabled, u.Filters.WebClient) {
863		return false
864	}
865	return u.HasAnyPerm(permsDeleteAny, target)
866}
867
868// GetSignature returns a signature for this admin.
869// It could change after an update
870func (u *User) GetSignature() string {
871	data := []byte(fmt.Sprintf("%v_%v_%v", u.Username, u.Status, u.ExpirationDate))
872	data = append(data, []byte(u.Password)...)
873	signature := sha256.Sum256(data)
874	return base64.StdEncoding.EncodeToString(signature[:])
875}
876
877// IsLoginFromAddrAllowed returns true if the login is allowed from the specified remoteAddr.
878// If AllowedIP is defined only the specified IP/Mask can login.
879// If DeniedIP is defined the specified IP/Mask cannot login.
880// If an IP is both allowed and denied then login will be denied
881func (u *User) IsLoginFromAddrAllowed(remoteAddr string) bool {
882	if len(u.Filters.AllowedIP) == 0 && len(u.Filters.DeniedIP) == 0 {
883		return true
884	}
885	remoteIP := net.ParseIP(util.GetIPFromRemoteAddress(remoteAddr))
886	// if remoteIP is invalid we allow login, this should never happen
887	if remoteIP == nil {
888		logger.Warn(logSender, "", "login allowed for invalid IP. remote address: %#v", remoteAddr)
889		return true
890	}
891	for _, IPMask := range u.Filters.DeniedIP {
892		_, IPNet, err := net.ParseCIDR(IPMask)
893		if err != nil {
894			return false
895		}
896		if IPNet.Contains(remoteIP) {
897			return false
898		}
899	}
900	for _, IPMask := range u.Filters.AllowedIP {
901		_, IPNet, err := net.ParseCIDR(IPMask)
902		if err != nil {
903			return false
904		}
905		if IPNet.Contains(remoteIP) {
906			return true
907		}
908	}
909	return len(u.Filters.AllowedIP) == 0
910}
911
912// GetPermissionsAsJSON returns the permissions as json byte array
913func (u *User) GetPermissionsAsJSON() ([]byte, error) {
914	return json.Marshal(u.Permissions)
915}
916
917// GetPublicKeysAsJSON returns the public keys as json byte array
918func (u *User) GetPublicKeysAsJSON() ([]byte, error) {
919	return json.Marshal(u.PublicKeys)
920}
921
922// GetFiltersAsJSON returns the filters as json byte array
923func (u *User) GetFiltersAsJSON() ([]byte, error) {
924	return json.Marshal(u.Filters)
925}
926
927// GetFsConfigAsJSON returns the filesystem config as json byte array
928func (u *User) GetFsConfigAsJSON() ([]byte, error) {
929	return json.Marshal(u.FsConfig)
930}
931
932// GetUID returns a validate uid, suitable for use with os.Chown
933func (u *User) GetUID() int {
934	if u.UID <= 0 || u.UID > math.MaxInt32 {
935		return -1
936	}
937	return u.UID
938}
939
940// GetGID returns a validate gid, suitable for use with os.Chown
941func (u *User) GetGID() int {
942	if u.GID <= 0 || u.GID > math.MaxInt32 {
943		return -1
944	}
945	return u.GID
946}
947
948// GetHomeDir returns the shortest path name equivalent to the user's home directory
949func (u *User) GetHomeDir() string {
950	return filepath.Clean(u.HomeDir)
951}
952
953// HasQuotaRestrictions returns true if there is a quota restriction on number of files or size or both
954func (u *User) HasQuotaRestrictions() bool {
955	return u.QuotaFiles > 0 || u.QuotaSize > 0
956}
957
958// GetQuotaSummary returns used quota and limits if defined
959func (u *User) GetQuotaSummary() string {
960	var result string
961	result = "Files: " + strconv.Itoa(u.UsedQuotaFiles)
962	if u.QuotaFiles > 0 {
963		result += "/" + strconv.Itoa(u.QuotaFiles)
964	}
965	if u.UsedQuotaSize > 0 || u.QuotaSize > 0 {
966		result += ". Size: " + util.ByteCountIEC(u.UsedQuotaSize)
967		if u.QuotaSize > 0 {
968			result += "/" + util.ByteCountIEC(u.QuotaSize)
969		}
970	}
971	if u.LastQuotaUpdate > 0 {
972		t := util.GetTimeFromMsecSinceEpoch(u.LastQuotaUpdate)
973		result += fmt.Sprintf(". Last update: %v ", t.Format("2006-01-02 15:04")) // YYYY-MM-DD HH:MM
974	}
975	return result
976}
977
978// GetPermissionsAsString returns the user's permissions as comma separated string
979func (u *User) GetPermissionsAsString() string {
980	result := ""
981	for dir, perms := range u.Permissions {
982		dirPerms := strings.Join(perms, ", ")
983		dp := fmt.Sprintf("%#v: %#v", dir, dirPerms)
984		if dir == "/" {
985			if result != "" {
986				result = dp + ", " + result
987			} else {
988				result = dp
989			}
990		} else {
991			if result != "" {
992				result += ", "
993			}
994			result += dp
995		}
996	}
997	return result
998}
999
1000// GetBandwidthAsString returns bandwidth limits if defines
1001func (u *User) GetBandwidthAsString() string {
1002	result := "DL: "
1003	if u.DownloadBandwidth > 0 {
1004		result += util.ByteCountIEC(u.DownloadBandwidth*1000) + "/s."
1005	} else {
1006		result += "unlimited."
1007	}
1008	result += " UL: "
1009	if u.UploadBandwidth > 0 {
1010		result += util.ByteCountIEC(u.UploadBandwidth*1000) + "/s."
1011	} else {
1012		result += "unlimited."
1013	}
1014	return result
1015}
1016
1017// GetInfoString returns user's info as string.
1018// Storage provider, number of public keys, max sessions, uid,
1019// gid, denied and allowed IP/Mask are returned
1020func (u *User) GetInfoString() string {
1021	var result strings.Builder
1022	if u.LastLogin > 0 {
1023		t := util.GetTimeFromMsecSinceEpoch(u.LastLogin)
1024		result.WriteString(fmt.Sprintf("Last login: %v. ", t.Format("2006-01-02 15:04"))) // YYYY-MM-DD HH:MM
1025	}
1026	if u.FsConfig.Provider != sdk.LocalFilesystemProvider {
1027		result.WriteString(fmt.Sprintf("Storage: %s. ", u.FsConfig.Provider.ShortInfo()))
1028	}
1029	if len(u.PublicKeys) > 0 {
1030		result.WriteString(fmt.Sprintf("Public keys: %v. ", len(u.PublicKeys)))
1031	}
1032	if u.MaxSessions > 0 {
1033		result.WriteString(fmt.Sprintf("Max sessions: %v. ", u.MaxSessions))
1034	}
1035	if u.UID > 0 {
1036		result.WriteString(fmt.Sprintf("UID: %v. ", u.UID))
1037	}
1038	if u.GID > 0 {
1039		result.WriteString(fmt.Sprintf("GID: %v. ", u.GID))
1040	}
1041	if len(u.Filters.DeniedIP) > 0 {
1042		result.WriteString(fmt.Sprintf("Denied IP/Mask: %v. ", len(u.Filters.DeniedIP)))
1043	}
1044	if len(u.Filters.AllowedIP) > 0 {
1045		result.WriteString(fmt.Sprintf("Allowed IP/Mask: %v", len(u.Filters.AllowedIP)))
1046	}
1047	return result.String()
1048}
1049
1050// GetStatusAsString returns the user status as a string
1051func (u *User) GetStatusAsString() string {
1052	if u.ExpirationDate > 0 && u.ExpirationDate < util.GetTimeAsMsSinceEpoch(time.Now()) {
1053		return "Expired"
1054	}
1055	if u.Status == 1 {
1056		return "Active"
1057	}
1058	return "Inactive"
1059}
1060
1061// GetExpirationDateAsString returns expiration date formatted as YYYY-MM-DD
1062func (u *User) GetExpirationDateAsString() string {
1063	if u.ExpirationDate > 0 {
1064		t := util.GetTimeFromMsecSinceEpoch(u.ExpirationDate)
1065		return t.Format("2006-01-02")
1066	}
1067	return ""
1068}
1069
1070// GetAllowedIPAsString returns the allowed IP as comma separated string
1071func (u *User) GetAllowedIPAsString() string {
1072	return strings.Join(u.Filters.AllowedIP, ",")
1073}
1074
1075// GetDeniedIPAsString returns the denied IP as comma separated string
1076func (u *User) GetDeniedIPAsString() string {
1077	return strings.Join(u.Filters.DeniedIP, ",")
1078}
1079
1080// CountUnusedRecoveryCodes returns the number of unused recovery codes
1081func (u *User) CountUnusedRecoveryCodes() int {
1082	unused := 0
1083	for _, code := range u.Filters.RecoveryCodes {
1084		if !code.Used {
1085			unused++
1086		}
1087	}
1088	return unused
1089}
1090
1091// SetEmptySecretsIfNil sets the secrets to empty if nil
1092func (u *User) SetEmptySecretsIfNil() {
1093	u.FsConfig.SetEmptySecretsIfNil()
1094	for idx := range u.VirtualFolders {
1095		vfolder := &u.VirtualFolders[idx]
1096		vfolder.FsConfig.SetEmptySecretsIfNil()
1097	}
1098	if u.Filters.TOTPConfig.Secret == nil {
1099		u.Filters.TOTPConfig.Secret = kms.NewEmptySecret()
1100	}
1101}
1102
1103func (u *User) getACopy() User {
1104	u.SetEmptySecretsIfNil()
1105	pubKeys := make([]string, len(u.PublicKeys))
1106	copy(pubKeys, u.PublicKeys)
1107	virtualFolders := make([]vfs.VirtualFolder, 0, len(u.VirtualFolders))
1108	for idx := range u.VirtualFolders {
1109		vfolder := u.VirtualFolders[idx].GetACopy()
1110		virtualFolders = append(virtualFolders, vfolder)
1111	}
1112	permissions := make(map[string][]string)
1113	for k, v := range u.Permissions {
1114		perms := make([]string, len(v))
1115		copy(perms, v)
1116		permissions[k] = perms
1117	}
1118	filters := sdk.UserFilters{}
1119	filters.MaxUploadFileSize = u.Filters.MaxUploadFileSize
1120	filters.TLSUsername = u.Filters.TLSUsername
1121	filters.UserType = u.Filters.UserType
1122	filters.TOTPConfig.Enabled = u.Filters.TOTPConfig.Enabled
1123	filters.TOTPConfig.ConfigName = u.Filters.TOTPConfig.ConfigName
1124	filters.TOTPConfig.Secret = u.Filters.TOTPConfig.Secret.Clone()
1125	filters.TOTPConfig.Protocols = make([]string, len(u.Filters.TOTPConfig.Protocols))
1126	copy(filters.TOTPConfig.Protocols, u.Filters.TOTPConfig.Protocols)
1127	filters.AllowedIP = make([]string, len(u.Filters.AllowedIP))
1128	copy(filters.AllowedIP, u.Filters.AllowedIP)
1129	filters.DeniedIP = make([]string, len(u.Filters.DeniedIP))
1130	copy(filters.DeniedIP, u.Filters.DeniedIP)
1131	filters.DeniedLoginMethods = make([]string, len(u.Filters.DeniedLoginMethods))
1132	copy(filters.DeniedLoginMethods, u.Filters.DeniedLoginMethods)
1133	filters.FilePatterns = make([]sdk.PatternsFilter, len(u.Filters.FilePatterns))
1134	copy(filters.FilePatterns, u.Filters.FilePatterns)
1135	filters.DeniedProtocols = make([]string, len(u.Filters.DeniedProtocols))
1136	copy(filters.DeniedProtocols, u.Filters.DeniedProtocols)
1137	filters.Hooks.ExternalAuthDisabled = u.Filters.Hooks.ExternalAuthDisabled
1138	filters.Hooks.PreLoginDisabled = u.Filters.Hooks.PreLoginDisabled
1139	filters.Hooks.CheckPasswordDisabled = u.Filters.Hooks.CheckPasswordDisabled
1140	filters.DisableFsChecks = u.Filters.DisableFsChecks
1141	filters.AllowAPIKeyAuth = u.Filters.AllowAPIKeyAuth
1142	filters.WebClient = make([]string, len(u.Filters.WebClient))
1143	copy(filters.WebClient, u.Filters.WebClient)
1144	filters.RecoveryCodes = make([]sdk.RecoveryCode, 0)
1145	for _, code := range u.Filters.RecoveryCodes {
1146		if code.Secret == nil {
1147			code.Secret = kms.NewEmptySecret()
1148		}
1149		filters.RecoveryCodes = append(filters.RecoveryCodes, sdk.RecoveryCode{
1150			Secret: code.Secret.Clone(),
1151			Used:   code.Used,
1152		})
1153	}
1154
1155	return User{
1156		BaseUser: sdk.BaseUser{
1157			ID:                u.ID,
1158			Username:          u.Username,
1159			Email:             u.Email,
1160			Password:          u.Password,
1161			PublicKeys:        pubKeys,
1162			HomeDir:           u.HomeDir,
1163			UID:               u.UID,
1164			GID:               u.GID,
1165			MaxSessions:       u.MaxSessions,
1166			QuotaSize:         u.QuotaSize,
1167			QuotaFiles:        u.QuotaFiles,
1168			Permissions:       permissions,
1169			UsedQuotaSize:     u.UsedQuotaSize,
1170			UsedQuotaFiles:    u.UsedQuotaFiles,
1171			LastQuotaUpdate:   u.LastQuotaUpdate,
1172			UploadBandwidth:   u.UploadBandwidth,
1173			DownloadBandwidth: u.DownloadBandwidth,
1174			Status:            u.Status,
1175			ExpirationDate:    u.ExpirationDate,
1176			LastLogin:         u.LastLogin,
1177			Filters:           filters,
1178			AdditionalInfo:    u.AdditionalInfo,
1179			Description:       u.Description,
1180			CreatedAt:         u.CreatedAt,
1181			UpdatedAt:         u.UpdatedAt,
1182		},
1183		VirtualFolders: virtualFolders,
1184		FsConfig:       u.FsConfig.GetACopy(),
1185	}
1186}
1187
1188// GetEncryptionAdditionalData returns the additional data to use for AEAD
1189func (u *User) GetEncryptionAdditionalData() string {
1190	return u.Username
1191}
1192
1193// GetGCSCredentialsFilePath returns the path for GCS credentials
1194func (u *User) GetGCSCredentialsFilePath() string {
1195	return filepath.Join(credentialsDirPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
1196}
1197