user.go 28 KB

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