user.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. package dataprovider
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "net"
  7. "os"
  8. "path"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "github.com/drakkan/sftpgo/logger"
  14. "github.com/drakkan/sftpgo/utils"
  15. "github.com/drakkan/sftpgo/vfs"
  16. )
  17. // Available permissions for SFTP users
  18. const (
  19. // All permissions are granted
  20. PermAny = "*"
  21. // List items such as files and directories is allowed
  22. PermListItems = "list"
  23. // download files is allowed
  24. PermDownload = "download"
  25. // upload files is allowed
  26. PermUpload = "upload"
  27. // overwrite an existing file, while uploading, is allowed
  28. // upload permission is required to allow file overwrite
  29. PermOverwrite = "overwrite"
  30. // delete files or directories is allowed
  31. PermDelete = "delete"
  32. // rename files or directories is allowed
  33. PermRename = "rename"
  34. // create directories is allowed
  35. PermCreateDirs = "create_dirs"
  36. // create symbolic links is allowed
  37. PermCreateSymlinks = "create_symlinks"
  38. // changing file or directory permissions is allowed
  39. PermChmod = "chmod"
  40. // changing file or directory owner and group is allowed
  41. PermChown = "chown"
  42. // changing file or directory access and modification time is allowed
  43. PermChtimes = "chtimes"
  44. )
  45. // Available login methods
  46. const (
  47. LoginMethodNoAuthTryed = "no_auth_tryed"
  48. LoginMethodPassword = "password"
  49. SSHLoginMethodPublicKey = "publickey"
  50. SSHLoginMethodKeyboardInteractive = "keyboard-interactive"
  51. SSHLoginMethodKeyAndPassword = "publickey+password"
  52. SSHLoginMethodKeyAndKeyboardInt = "publickey+keyboard-interactive"
  53. )
  54. var (
  55. errNoMatchingVirtualFolder = errors.New("no matching virtual folder found")
  56. )
  57. // CachedUser adds fields useful for caching to a SFTPGo user
  58. type CachedUser struct {
  59. User User
  60. Expiration time.Time
  61. Password string
  62. }
  63. // IsExpired returns true if the cached user is expired
  64. func (c CachedUser) IsExpired() bool {
  65. if c.Expiration.IsZero() {
  66. return false
  67. }
  68. return c.Expiration.Before(time.Now())
  69. }
  70. // ExtensionsFilter defines filters based on file extensions.
  71. // These restrictions do not apply to files listing for performance reasons, so
  72. // a denied file cannot be downloaded/overwritten/renamed but will still be
  73. // it will still be listed in the list of files.
  74. // System commands such as Git and rsync interacts with the filesystem directly
  75. // and they are not aware about these restrictions so they are not allowed
  76. // inside paths with extensions filters
  77. type ExtensionsFilter struct {
  78. // SFTP/SCP path, if no other specific filter is defined, the filter apply for
  79. // sub directories too.
  80. // For example if filters are defined for the paths "/" and "/sub" then the
  81. // filters for "/" are applied for any file outside the "/sub" directory
  82. Path string `json:"path"`
  83. // only files with these, case insensitive, extensions are allowed.
  84. // Shell like expansion is not supported so you have to specify ".jpg" and
  85. // not "*.jpg"
  86. AllowedExtensions []string `json:"allowed_extensions,omitempty"`
  87. // files with these, case insensitive, extensions are not allowed.
  88. // Denied file extensions are evaluated before the allowed ones
  89. DeniedExtensions []string `json:"denied_extensions,omitempty"`
  90. }
  91. // UserFilters defines additional restrictions for a user
  92. type UserFilters struct {
  93. // only clients connecting from these IP/Mask are allowed.
  94. // IP/Mask must be in CIDR notation as defined in RFC 4632 and RFC 4291
  95. // for example "192.0.2.0/24" or "2001:db8::/32"
  96. AllowedIP []string `json:"allowed_ip,omitempty"`
  97. // clients connecting from these IP/Mask are not allowed.
  98. // Denied rules will be evaluated before allowed ones
  99. DeniedIP []string `json:"denied_ip,omitempty"`
  100. // these login methods are not allowed.
  101. // If null or empty any available login method is allowed
  102. DeniedLoginMethods []string `json:"denied_login_methods,omitempty"`
  103. // these protocols are not allowed.
  104. // If null or empty any available protocol is allowed
  105. DeniedProtocols []string `json:"denied_protocols,omitempty"`
  106. // filters based on file extensions.
  107. // Please note that these restrictions can be easily bypassed.
  108. FileExtensions []ExtensionsFilter `json:"file_extensions,omitempty"`
  109. // max size allowed for a single upload, 0 means unlimited
  110. MaxUploadFileSize int64 `json:"max_upload_file_size,omitempty"`
  111. }
  112. // FilesystemProvider defines the supported storages
  113. type FilesystemProvider int
  114. // supported values for FilesystemProvider
  115. const (
  116. LocalFilesystemProvider FilesystemProvider = iota // Local
  117. S3FilesystemProvider // Amazon S3 compatible
  118. GCSFilesystemProvider // Google Cloud Storage
  119. )
  120. // Filesystem defines cloud storage filesystem details
  121. type Filesystem struct {
  122. Provider FilesystemProvider `json:"provider"`
  123. S3Config vfs.S3FsConfig `json:"s3config,omitempty"`
  124. GCSConfig vfs.GCSFsConfig `json:"gcsconfig,omitempty"`
  125. }
  126. // User defines a SFTPGo user
  127. type User struct {
  128. // Database unique identifier
  129. ID int64 `json:"id"`
  130. // 1 enabled, 0 disabled (login is not allowed)
  131. Status int `json:"status"`
  132. // Username
  133. Username string `json:"username"`
  134. // Account expiration date as unix timestamp in milliseconds. An expired account cannot login.
  135. // 0 means no expiration
  136. ExpirationDate int64 `json:"expiration_date"`
  137. // Password used for password authentication.
  138. // For users created using SFTPGo REST API the password is be stored using argon2id hashing algo.
  139. // Checking passwords stored with bcrypt, pbkdf2, md5crypt and sha512crypt is supported too.
  140. Password string `json:"password,omitempty"`
  141. // PublicKeys used for public key authentication. At least one between password and a public key is mandatory
  142. PublicKeys []string `json:"public_keys,omitempty"`
  143. // The user cannot upload or download files outside this directory. Must be an absolute path
  144. HomeDir string `json:"home_dir"`
  145. // Mapping between virtual paths and filesystem paths outside the home directory.
  146. // Supported for local filesystem only
  147. VirtualFolders []vfs.VirtualFolder `json:"virtual_folders,omitempty"`
  148. // If sftpgo runs as root system user then the created files and directories will be assigned to this system UID
  149. UID int `json:"uid"`
  150. // If sftpgo runs as root system user then the created files and directories will be assigned to this system GID
  151. GID int `json:"gid"`
  152. // Maximum concurrent sessions. 0 means unlimited
  153. MaxSessions int `json:"max_sessions"`
  154. // Maximum size allowed as bytes. 0 means unlimited
  155. QuotaSize int64 `json:"quota_size"`
  156. // Maximum number of files allowed. 0 means unlimited
  157. QuotaFiles int `json:"quota_files"`
  158. // List of the granted permissions
  159. Permissions map[string][]string `json:"permissions"`
  160. // Used quota as bytes
  161. UsedQuotaSize int64 `json:"used_quota_size"`
  162. // Used quota as number of files
  163. UsedQuotaFiles int `json:"used_quota_files"`
  164. // Last quota update as unix timestamp in milliseconds
  165. LastQuotaUpdate int64 `json:"last_quota_update"`
  166. // Maximum upload bandwidth as KB/s, 0 means unlimited
  167. UploadBandwidth int64 `json:"upload_bandwidth"`
  168. // Maximum download bandwidth as KB/s, 0 means unlimited
  169. DownloadBandwidth int64 `json:"download_bandwidth"`
  170. // Last login as unix timestamp in milliseconds
  171. LastLogin int64 `json:"last_login"`
  172. // Additional restrictions
  173. Filters UserFilters `json:"filters"`
  174. // Filesystem configuration details
  175. FsConfig Filesystem `json:"filesystem"`
  176. }
  177. // GetFilesystem returns the filesystem for this user
  178. func (u *User) GetFilesystem(connectionID string) (vfs.Fs, error) {
  179. if u.FsConfig.Provider == S3FilesystemProvider {
  180. return vfs.NewS3Fs(connectionID, u.GetHomeDir(), u.FsConfig.S3Config)
  181. } else if u.FsConfig.Provider == GCSFilesystemProvider {
  182. config := u.FsConfig.GCSConfig
  183. config.CredentialFile = u.getGCSCredentialsFilePath()
  184. return vfs.NewGCSFs(connectionID, u.GetHomeDir(), config)
  185. }
  186. return vfs.NewOsFs(connectionID, u.GetHomeDir(), u.VirtualFolders), nil
  187. }
  188. // GetPermissionsForPath returns the permissions for the given path.
  189. // The path must be an SFTP path
  190. func (u *User) GetPermissionsForPath(p string) []string {
  191. permissions := []string{}
  192. if perms, ok := u.Permissions["/"]; ok {
  193. // if only root permissions are defined returns them unconditionally
  194. if len(u.Permissions) == 1 {
  195. return perms
  196. }
  197. // fallback permissions
  198. permissions = perms
  199. }
  200. dirsForPath := utils.GetDirsForSFTPPath(p)
  201. // dirsForPath contains all the dirs for a given path in reverse order
  202. // for example if the path is: /1/2/3/4 it contains:
  203. // [ "/1/2/3/4", "/1/2/3", "/1/2", "/1", "/" ]
  204. // so the first match is the one we are interested to
  205. for _, val := range dirsForPath {
  206. if perms, ok := u.Permissions[val]; ok {
  207. permissions = perms
  208. break
  209. }
  210. }
  211. return permissions
  212. }
  213. // GetVirtualFolderForPath returns the virtual folder containing the specified sftp path.
  214. // If the path is not inside a virtual folder an error is returned
  215. func (u *User) GetVirtualFolderForPath(sftpPath string) (vfs.VirtualFolder, error) {
  216. var folder vfs.VirtualFolder
  217. if len(u.VirtualFolders) == 0 || u.FsConfig.Provider != LocalFilesystemProvider {
  218. return folder, errNoMatchingVirtualFolder
  219. }
  220. dirsForPath := utils.GetDirsForSFTPPath(sftpPath)
  221. for _, val := range dirsForPath {
  222. for _, v := range u.VirtualFolders {
  223. if v.VirtualPath == val {
  224. return v, nil
  225. }
  226. }
  227. }
  228. return folder, errNoMatchingVirtualFolder
  229. }
  230. // AddVirtualDirs adds virtual folders, if defined, to the given files list
  231. func (u *User) AddVirtualDirs(list []os.FileInfo, sftpPath string) []os.FileInfo {
  232. if len(u.VirtualFolders) == 0 {
  233. return list
  234. }
  235. for _, v := range u.VirtualFolders {
  236. if path.Dir(v.VirtualPath) == sftpPath {
  237. fi := vfs.NewFileInfo(v.VirtualPath, true, 0, time.Now(), false)
  238. found := false
  239. for index, f := range list {
  240. if f.Name() == fi.Name() {
  241. list[index] = fi
  242. found = true
  243. break
  244. }
  245. }
  246. if !found {
  247. list = append(list, fi)
  248. }
  249. }
  250. }
  251. return list
  252. }
  253. // IsMappedPath returns true if the specified filesystem path has a virtual folder mapping.
  254. // The filesystem path must be cleaned before calling this method
  255. func (u *User) IsMappedPath(fsPath string) bool {
  256. for _, v := range u.VirtualFolders {
  257. if fsPath == v.MappedPath {
  258. return true
  259. }
  260. }
  261. return false
  262. }
  263. // IsVirtualFolder returns true if the specified sftp path is a virtual folder
  264. func (u *User) IsVirtualFolder(sftpPath string) bool {
  265. for _, v := range u.VirtualFolders {
  266. if sftpPath == v.VirtualPath {
  267. return true
  268. }
  269. }
  270. return false
  271. }
  272. // HasVirtualFoldersInside returns true if there are virtual folders inside the
  273. // specified SFTP path. We assume that path are cleaned
  274. func (u *User) HasVirtualFoldersInside(sftpPath string) bool {
  275. if sftpPath == "/" && len(u.VirtualFolders) > 0 {
  276. return true
  277. }
  278. for _, v := range u.VirtualFolders {
  279. if len(v.VirtualPath) > len(sftpPath) {
  280. if strings.HasPrefix(v.VirtualPath, sftpPath+"/") {
  281. return true
  282. }
  283. }
  284. }
  285. return false
  286. }
  287. // HasPermissionsInside returns true if the specified sftpPath has no permissions itself and
  288. // no subdirs with defined permissions
  289. func (u *User) HasPermissionsInside(sftpPath string) bool {
  290. for dir := range u.Permissions {
  291. if dir == sftpPath {
  292. return true
  293. } else if len(dir) > len(sftpPath) {
  294. if strings.HasPrefix(dir, sftpPath+"/") {
  295. return true
  296. }
  297. }
  298. }
  299. return false
  300. }
  301. // HasOverlappedMappedPaths returns true if this user has virtual folders with overlapped mapped paths
  302. func (u *User) HasOverlappedMappedPaths() bool {
  303. if len(u.VirtualFolders) <= 1 {
  304. return false
  305. }
  306. for _, v1 := range u.VirtualFolders {
  307. for _, v2 := range u.VirtualFolders {
  308. if v1.VirtualPath == v2.VirtualPath {
  309. continue
  310. }
  311. if isMappedDirOverlapped(v1.MappedPath, v2.MappedPath) {
  312. return true
  313. }
  314. }
  315. }
  316. return false
  317. }
  318. // HasPerm returns true if the user has the given permission or any permission
  319. func (u *User) HasPerm(permission, path string) bool {
  320. perms := u.GetPermissionsForPath(path)
  321. if utils.IsStringInSlice(PermAny, perms) {
  322. return true
  323. }
  324. return utils.IsStringInSlice(permission, perms)
  325. }
  326. // HasPerms return true if the user has all the given permissions
  327. func (u *User) HasPerms(permissions []string, path string) bool {
  328. perms := u.GetPermissionsForPath(path)
  329. if utils.IsStringInSlice(PermAny, perms) {
  330. return true
  331. }
  332. for _, permission := range permissions {
  333. if !utils.IsStringInSlice(permission, perms) {
  334. return false
  335. }
  336. }
  337. return true
  338. }
  339. // HasNoQuotaRestrictions returns true if no quota restrictions need to be applyed
  340. func (u *User) HasNoQuotaRestrictions(checkFiles bool) bool {
  341. if u.QuotaSize == 0 && (!checkFiles || u.QuotaFiles == 0) {
  342. return true
  343. }
  344. return false
  345. }
  346. // IsLoginMethodAllowed returns true if the specified login method is allowed
  347. func (u *User) IsLoginMethodAllowed(loginMethod string, partialSuccessMethods []string) bool {
  348. if len(u.Filters.DeniedLoginMethods) == 0 {
  349. return true
  350. }
  351. if len(partialSuccessMethods) == 1 {
  352. for _, method := range u.GetNextAuthMethods(partialSuccessMethods, true) {
  353. if method == loginMethod {
  354. return true
  355. }
  356. }
  357. }
  358. if utils.IsStringInSlice(loginMethod, u.Filters.DeniedLoginMethods) {
  359. return false
  360. }
  361. return true
  362. }
  363. // GetNextAuthMethods returns the list of authentications methods that
  364. // can continue for multi-step authentication
  365. func (u *User) GetNextAuthMethods(partialSuccessMethods []string, isPasswordAuthEnabled bool) []string {
  366. var methods []string
  367. if len(partialSuccessMethods) != 1 {
  368. return methods
  369. }
  370. if partialSuccessMethods[0] != SSHLoginMethodPublicKey {
  371. return methods
  372. }
  373. for _, method := range u.GetAllowedLoginMethods() {
  374. if method == SSHLoginMethodKeyAndPassword && isPasswordAuthEnabled {
  375. methods = append(methods, LoginMethodPassword)
  376. }
  377. if method == SSHLoginMethodKeyAndKeyboardInt {
  378. methods = append(methods, SSHLoginMethodKeyboardInteractive)
  379. }
  380. }
  381. return methods
  382. }
  383. // IsPartialAuth returns true if the specified login method is a step for
  384. // a multi-step Authentication.
  385. // We support publickey+password and publickey+keyboard-interactive, so
  386. // only publickey can returns partial success.
  387. // We can have partial success if only multi-step Auth methods are enabled
  388. func (u *User) IsPartialAuth(loginMethod string) bool {
  389. if loginMethod != SSHLoginMethodPublicKey {
  390. return false
  391. }
  392. for _, method := range u.GetAllowedLoginMethods() {
  393. if !utils.IsStringInSlice(method, SSHMultiStepsLoginMethods) {
  394. return false
  395. }
  396. }
  397. return true
  398. }
  399. // GetAllowedLoginMethods returns the allowed login methods
  400. func (u *User) GetAllowedLoginMethods() []string {
  401. var allowedMethods []string
  402. for _, method := range ValidSSHLoginMethods {
  403. if !utils.IsStringInSlice(method, u.Filters.DeniedLoginMethods) {
  404. allowedMethods = append(allowedMethods, method)
  405. }
  406. }
  407. return allowedMethods
  408. }
  409. // IsFileAllowed returns true if the specified file is allowed by the file restrictions filters
  410. func (u *User) IsFileAllowed(sftpPath string) bool {
  411. if len(u.Filters.FileExtensions) == 0 {
  412. return true
  413. }
  414. dirsForPath := utils.GetDirsForSFTPPath(path.Dir(sftpPath))
  415. var filter ExtensionsFilter
  416. for _, dir := range dirsForPath {
  417. for _, f := range u.Filters.FileExtensions {
  418. if f.Path == dir {
  419. filter = f
  420. break
  421. }
  422. }
  423. if len(filter.Path) > 0 {
  424. break
  425. }
  426. }
  427. if len(filter.Path) > 0 {
  428. toMatch := strings.ToLower(sftpPath)
  429. for _, denied := range filter.DeniedExtensions {
  430. if strings.HasSuffix(toMatch, denied) {
  431. return false
  432. }
  433. }
  434. for _, allowed := range filter.AllowedExtensions {
  435. if strings.HasSuffix(toMatch, allowed) {
  436. return true
  437. }
  438. }
  439. return len(filter.AllowedExtensions) == 0
  440. }
  441. return true
  442. }
  443. // IsLoginFromAddrAllowed returns true if the login is allowed from the specified remoteAddr.
  444. // If AllowedIP is defined only the specified IP/Mask can login.
  445. // If DeniedIP is defined the specified IP/Mask cannot login.
  446. // If an IP is both allowed and denied then login will be denied
  447. func (u *User) IsLoginFromAddrAllowed(remoteAddr string) bool {
  448. if len(u.Filters.AllowedIP) == 0 && len(u.Filters.DeniedIP) == 0 {
  449. return true
  450. }
  451. remoteIP := net.ParseIP(utils.GetIPFromRemoteAddress(remoteAddr))
  452. // if remoteIP is invalid we allow login, this should never happen
  453. if remoteIP == nil {
  454. logger.Warn(logSender, "", "login allowed for invalid IP. remote address: %#v", remoteAddr)
  455. return true
  456. }
  457. for _, IPMask := range u.Filters.DeniedIP {
  458. _, IPNet, err := net.ParseCIDR(IPMask)
  459. if err != nil {
  460. return false
  461. }
  462. if IPNet.Contains(remoteIP) {
  463. return false
  464. }
  465. }
  466. for _, IPMask := range u.Filters.AllowedIP {
  467. _, IPNet, err := net.ParseCIDR(IPMask)
  468. if err != nil {
  469. return false
  470. }
  471. if IPNet.Contains(remoteIP) {
  472. return true
  473. }
  474. }
  475. return len(u.Filters.AllowedIP) == 0
  476. }
  477. // GetPermissionsAsJSON returns the permissions as json byte array
  478. func (u *User) GetPermissionsAsJSON() ([]byte, error) {
  479. return json.Marshal(u.Permissions)
  480. }
  481. // GetPublicKeysAsJSON returns the public keys as json byte array
  482. func (u *User) GetPublicKeysAsJSON() ([]byte, error) {
  483. return json.Marshal(u.PublicKeys)
  484. }
  485. // GetFiltersAsJSON returns the filters as json byte array
  486. func (u *User) GetFiltersAsJSON() ([]byte, error) {
  487. return json.Marshal(u.Filters)
  488. }
  489. // GetFsConfigAsJSON returns the filesystem config as json byte array
  490. func (u *User) GetFsConfigAsJSON() ([]byte, error) {
  491. return json.Marshal(u.FsConfig)
  492. }
  493. // GetUID returns a validate uid, suitable for use with os.Chown
  494. func (u *User) GetUID() int {
  495. if u.UID <= 0 || u.UID > 65535 {
  496. return -1
  497. }
  498. return u.UID
  499. }
  500. // GetGID returns a validate gid, suitable for use with os.Chown
  501. func (u *User) GetGID() int {
  502. if u.GID <= 0 || u.GID > 65535 {
  503. return -1
  504. }
  505. return u.GID
  506. }
  507. // GetHomeDir returns the shortest path name equivalent to the user's home directory
  508. func (u *User) GetHomeDir() string {
  509. return filepath.Clean(u.HomeDir)
  510. }
  511. // HasQuotaRestrictions returns true if there is a quota restriction on number of files or size or both
  512. func (u *User) HasQuotaRestrictions() bool {
  513. return u.QuotaFiles > 0 || u.QuotaSize > 0
  514. }
  515. // GetQuotaSummary returns used quota and limits if defined
  516. func (u *User) GetQuotaSummary() string {
  517. var result string
  518. result = "Files: " + strconv.Itoa(u.UsedQuotaFiles)
  519. if u.QuotaFiles > 0 {
  520. result += "/" + strconv.Itoa(u.QuotaFiles)
  521. }
  522. if u.UsedQuotaSize > 0 || u.QuotaSize > 0 {
  523. result += ". Size: " + utils.ByteCountSI(u.UsedQuotaSize)
  524. if u.QuotaSize > 0 {
  525. result += "/" + utils.ByteCountSI(u.QuotaSize)
  526. }
  527. }
  528. return result
  529. }
  530. // GetPermissionsAsString returns the user's permissions as comma separated string
  531. func (u *User) GetPermissionsAsString() string {
  532. result := ""
  533. for dir, perms := range u.Permissions {
  534. var dirPerms string
  535. for _, p := range perms {
  536. if len(dirPerms) > 0 {
  537. dirPerms += ", "
  538. }
  539. dirPerms += p
  540. }
  541. dp := fmt.Sprintf("%#v: %#v", dir, dirPerms)
  542. if dir == "/" {
  543. if len(result) > 0 {
  544. result = dp + ", " + result
  545. } else {
  546. result = dp
  547. }
  548. } else {
  549. if len(result) > 0 {
  550. result += ", "
  551. }
  552. result += dp
  553. }
  554. }
  555. return result
  556. }
  557. // GetBandwidthAsString returns bandwidth limits if defines
  558. func (u *User) GetBandwidthAsString() string {
  559. result := "Download: "
  560. if u.DownloadBandwidth > 0 {
  561. result += utils.ByteCountSI(u.DownloadBandwidth*1000) + "/s."
  562. } else {
  563. result += "unlimited."
  564. }
  565. result += " Upload: "
  566. if u.UploadBandwidth > 0 {
  567. result += utils.ByteCountSI(u.UploadBandwidth*1000) + "/s."
  568. } else {
  569. result += "unlimited."
  570. }
  571. return result
  572. }
  573. // GetInfoString returns user's info as string.
  574. // Storage provider, number of public keys, max sessions, uid,
  575. // gid, denied and allowed IP/Mask are returned
  576. func (u *User) GetInfoString() string {
  577. var result string
  578. if u.LastLogin > 0 {
  579. t := utils.GetTimeFromMsecSinceEpoch(u.LastLogin)
  580. result += fmt.Sprintf("Last login: %v ", t.Format("2006-01-02 15:04:05")) // YYYY-MM-DD HH:MM:SS
  581. }
  582. if u.FsConfig.Provider == S3FilesystemProvider {
  583. result += "Storage: S3 "
  584. } else if u.FsConfig.Provider == GCSFilesystemProvider {
  585. result += "Storage: GCS "
  586. }
  587. if len(u.PublicKeys) > 0 {
  588. result += fmt.Sprintf("Public keys: %v ", len(u.PublicKeys))
  589. }
  590. if u.MaxSessions > 0 {
  591. result += fmt.Sprintf("Max sessions: %v ", u.MaxSessions)
  592. }
  593. if u.UID > 0 {
  594. result += fmt.Sprintf("UID: %v ", u.UID)
  595. }
  596. if u.GID > 0 {
  597. result += fmt.Sprintf("GID: %v ", u.GID)
  598. }
  599. if len(u.Filters.DeniedIP) > 0 {
  600. result += fmt.Sprintf("Denied IP/Mask: %v ", len(u.Filters.DeniedIP))
  601. }
  602. if len(u.Filters.AllowedIP) > 0 {
  603. result += fmt.Sprintf("Allowed IP/Mask: %v ", len(u.Filters.AllowedIP))
  604. }
  605. return result
  606. }
  607. // GetExpirationDateAsString returns expiration date formatted as YYYY-MM-DD
  608. func (u *User) GetExpirationDateAsString() string {
  609. if u.ExpirationDate > 0 {
  610. t := utils.GetTimeFromMsecSinceEpoch(u.ExpirationDate)
  611. return t.Format("2006-01-02")
  612. }
  613. return ""
  614. }
  615. // GetAllowedIPAsString returns the allowed IP as comma separated string
  616. func (u User) GetAllowedIPAsString() string {
  617. result := ""
  618. for _, IPMask := range u.Filters.AllowedIP {
  619. if len(result) > 0 {
  620. result += ","
  621. }
  622. result += IPMask
  623. }
  624. return result
  625. }
  626. // GetDeniedIPAsString returns the denied IP as comma separated string
  627. func (u User) GetDeniedIPAsString() string {
  628. result := ""
  629. for _, IPMask := range u.Filters.DeniedIP {
  630. if len(result) > 0 {
  631. result += ","
  632. }
  633. result += IPMask
  634. }
  635. return result
  636. }
  637. func (u *User) getACopy() User {
  638. pubKeys := make([]string, len(u.PublicKeys))
  639. copy(pubKeys, u.PublicKeys)
  640. virtualFolders := make([]vfs.VirtualFolder, len(u.VirtualFolders))
  641. copy(virtualFolders, u.VirtualFolders)
  642. permissions := make(map[string][]string)
  643. for k, v := range u.Permissions {
  644. perms := make([]string, len(v))
  645. copy(perms, v)
  646. permissions[k] = perms
  647. }
  648. filters := UserFilters{}
  649. filters.MaxUploadFileSize = u.Filters.MaxUploadFileSize
  650. filters.AllowedIP = make([]string, len(u.Filters.AllowedIP))
  651. copy(filters.AllowedIP, u.Filters.AllowedIP)
  652. filters.DeniedIP = make([]string, len(u.Filters.DeniedIP))
  653. copy(filters.DeniedIP, u.Filters.DeniedIP)
  654. filters.DeniedLoginMethods = make([]string, len(u.Filters.DeniedLoginMethods))
  655. copy(filters.DeniedLoginMethods, u.Filters.DeniedLoginMethods)
  656. filters.FileExtensions = make([]ExtensionsFilter, len(u.Filters.FileExtensions))
  657. copy(filters.FileExtensions, u.Filters.FileExtensions)
  658. filters.DeniedProtocols = make([]string, len(u.Filters.DeniedProtocols))
  659. copy(filters.DeniedProtocols, u.Filters.DeniedProtocols)
  660. fsConfig := Filesystem{
  661. Provider: u.FsConfig.Provider,
  662. S3Config: vfs.S3FsConfig{
  663. Bucket: u.FsConfig.S3Config.Bucket,
  664. Region: u.FsConfig.S3Config.Region,
  665. AccessKey: u.FsConfig.S3Config.AccessKey,
  666. AccessSecret: u.FsConfig.S3Config.AccessSecret,
  667. Endpoint: u.FsConfig.S3Config.Endpoint,
  668. StorageClass: u.FsConfig.S3Config.StorageClass,
  669. KeyPrefix: u.FsConfig.S3Config.KeyPrefix,
  670. UploadPartSize: u.FsConfig.S3Config.UploadPartSize,
  671. UploadConcurrency: u.FsConfig.S3Config.UploadConcurrency,
  672. },
  673. GCSConfig: vfs.GCSFsConfig{
  674. Bucket: u.FsConfig.GCSConfig.Bucket,
  675. CredentialFile: u.FsConfig.GCSConfig.CredentialFile,
  676. Credentials: u.FsConfig.GCSConfig.Credentials,
  677. AutomaticCredentials: u.FsConfig.GCSConfig.AutomaticCredentials,
  678. StorageClass: u.FsConfig.GCSConfig.StorageClass,
  679. KeyPrefix: u.FsConfig.GCSConfig.KeyPrefix,
  680. },
  681. }
  682. return User{
  683. ID: u.ID,
  684. Username: u.Username,
  685. Password: u.Password,
  686. PublicKeys: pubKeys,
  687. HomeDir: u.HomeDir,
  688. VirtualFolders: virtualFolders,
  689. UID: u.UID,
  690. GID: u.GID,
  691. MaxSessions: u.MaxSessions,
  692. QuotaSize: u.QuotaSize,
  693. QuotaFiles: u.QuotaFiles,
  694. Permissions: permissions,
  695. UsedQuotaSize: u.UsedQuotaSize,
  696. UsedQuotaFiles: u.UsedQuotaFiles,
  697. LastQuotaUpdate: u.LastQuotaUpdate,
  698. UploadBandwidth: u.UploadBandwidth,
  699. DownloadBandwidth: u.DownloadBandwidth,
  700. Status: u.Status,
  701. ExpirationDate: u.ExpirationDate,
  702. LastLogin: u.LastLogin,
  703. Filters: filters,
  704. FsConfig: fsConfig,
  705. }
  706. }
  707. func (u *User) getNotificationFieldsAsSlice(action string) []string {
  708. return []string{action, u.Username,
  709. strconv.FormatInt(u.ID, 10),
  710. strconv.FormatInt(int64(u.Status), 10),
  711. strconv.FormatInt(u.ExpirationDate, 10),
  712. u.HomeDir,
  713. strconv.FormatInt(int64(u.UID), 10),
  714. strconv.FormatInt(int64(u.GID), 10),
  715. }
  716. }
  717. func (u *User) getNotificationFieldsAsEnvVars(action string) []string {
  718. return []string{fmt.Sprintf("SFTPGO_USER_ACTION=%v", action),
  719. fmt.Sprintf("SFTPGO_USER_USERNAME=%v", u.Username),
  720. fmt.Sprintf("SFTPGO_USER_PASSWORD=%v", u.Password),
  721. fmt.Sprintf("SFTPGO_USER_ID=%v", u.ID),
  722. fmt.Sprintf("SFTPGO_USER_STATUS=%v", u.Status),
  723. fmt.Sprintf("SFTPGO_USER_EXPIRATION_DATE=%v", u.ExpirationDate),
  724. fmt.Sprintf("SFTPGO_USER_HOME_DIR=%v", u.HomeDir),
  725. fmt.Sprintf("SFTPGO_USER_UID=%v", u.UID),
  726. fmt.Sprintf("SFTPGO_USER_GID=%v", u.GID),
  727. fmt.Sprintf("SFTPGO_USER_QUOTA_FILES=%v", u.QuotaFiles),
  728. fmt.Sprintf("SFTPGO_USER_QUOTA_SIZE=%v", u.QuotaSize),
  729. fmt.Sprintf("SFTPGO_USER_UPLOAD_BANDWIDTH=%v", u.UploadBandwidth),
  730. fmt.Sprintf("SFTPGO_USER_DOWNLOAD_BANDWIDTH=%v", u.DownloadBandwidth),
  731. fmt.Sprintf("SFTPGO_USER_MAX_SESSIONS=%v", u.MaxSessions),
  732. fmt.Sprintf("SFTPGO_USER_FS_PROVIDER=%v", u.FsConfig.Provider)}
  733. }
  734. func (u *User) getGCSCredentialsFilePath() string {
  735. return filepath.Join(credentialsDirPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
  736. }