dataretention_test.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. package common
  2. import (
  3. "errors"
  4. "fmt"
  5. "os/exec"
  6. "runtime"
  7. "testing"
  8. "time"
  9. "github.com/sftpgo/sdk"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "github.com/drakkan/sftpgo/v2/dataprovider"
  13. "github.com/drakkan/sftpgo/v2/smtp"
  14. )
  15. func TestRetentionValidation(t *testing.T) {
  16. check := RetentionCheck{}
  17. check.Folders = append(check.Folders, FolderRetention{
  18. Path: "relative",
  19. Retention: 10,
  20. })
  21. err := check.Validate()
  22. require.Error(t, err)
  23. assert.Contains(t, err.Error(), "please specify an absolute POSIX path")
  24. check.Folders = []FolderRetention{
  25. {
  26. Path: "/",
  27. Retention: -1,
  28. },
  29. }
  30. err = check.Validate()
  31. require.Error(t, err)
  32. assert.Contains(t, err.Error(), "invalid folder retention")
  33. check.Folders = []FolderRetention{
  34. {
  35. Path: "/ab/..",
  36. Retention: 0,
  37. },
  38. }
  39. err = check.Validate()
  40. require.Error(t, err)
  41. assert.Contains(t, err.Error(), "nothing to delete")
  42. assert.Equal(t, "/", check.Folders[0].Path)
  43. check.Folders = append(check.Folders, FolderRetention{
  44. Path: "/../..",
  45. Retention: 24,
  46. })
  47. err = check.Validate()
  48. require.Error(t, err)
  49. assert.Contains(t, err.Error(), `duplicated folder path "/"`)
  50. check.Folders = []FolderRetention{
  51. {
  52. Path: "/dir1",
  53. Retention: 48,
  54. },
  55. {
  56. Path: "/dir2",
  57. Retention: 96,
  58. },
  59. }
  60. err = check.Validate()
  61. assert.NoError(t, err)
  62. assert.Len(t, check.Notifications, 0)
  63. assert.Empty(t, check.Email)
  64. check.Notifications = []RetentionCheckNotification{RetentionCheckNotificationEmail}
  65. err = check.Validate()
  66. require.Error(t, err)
  67. assert.Contains(t, err.Error(), "you must configure an SMTP server")
  68. smtpCfg := smtp.Config{
  69. Host: "mail.example.com",
  70. Port: 25,
  71. TemplatesPath: "templates",
  72. }
  73. err = smtpCfg.Initialize("..")
  74. require.NoError(t, err)
  75. err = check.Validate()
  76. require.Error(t, err)
  77. assert.Contains(t, err.Error(), "you must add a valid email address")
  78. check.Email = "admin@example.com"
  79. err = check.Validate()
  80. assert.NoError(t, err)
  81. smtpCfg = smtp.Config{}
  82. err = smtpCfg.Initialize("..")
  83. require.NoError(t, err)
  84. check.Notifications = []RetentionCheckNotification{RetentionCheckNotificationHook}
  85. err = check.Validate()
  86. require.Error(t, err)
  87. assert.Contains(t, err.Error(), "data_retention_hook")
  88. check.Notifications = []string{"not valid"}
  89. err = check.Validate()
  90. require.Error(t, err)
  91. assert.Contains(t, err.Error(), "invalid notification")
  92. }
  93. func TestRetentionEmailNotifications(t *testing.T) {
  94. smtpCfg := smtp.Config{
  95. Host: "127.0.0.1",
  96. Port: 2525,
  97. TemplatesPath: "templates",
  98. }
  99. err := smtpCfg.Initialize("..")
  100. require.NoError(t, err)
  101. user := dataprovider.User{
  102. BaseUser: sdk.BaseUser{
  103. Username: "user1",
  104. },
  105. }
  106. user.Permissions = make(map[string][]string)
  107. user.Permissions["/"] = []string{dataprovider.PermAny}
  108. check := RetentionCheck{
  109. Notifications: []RetentionCheckNotification{RetentionCheckNotificationEmail},
  110. Email: "notification@example.com",
  111. results: []*folderRetentionCheckResult{
  112. {
  113. Path: "/",
  114. Retention: 24,
  115. DeletedFiles: 10,
  116. DeletedSize: 32657,
  117. Elapsed: 10 * time.Second,
  118. },
  119. },
  120. }
  121. conn := NewBaseConnection("", "", "", "", user)
  122. conn.SetProtocol(ProtocolDataRetention)
  123. conn.ID = fmt.Sprintf("data_retention_%v", user.Username)
  124. check.conn = conn
  125. check.sendNotifications(1*time.Second, nil)
  126. err = check.sendEmailNotification(1*time.Second, nil)
  127. assert.NoError(t, err)
  128. err = check.sendEmailNotification(1*time.Second, errors.New("test error"))
  129. assert.NoError(t, err)
  130. smtpCfg.Port = 2626
  131. err = smtpCfg.Initialize("..")
  132. require.NoError(t, err)
  133. err = check.sendEmailNotification(1*time.Second, nil)
  134. assert.Error(t, err)
  135. smtpCfg = smtp.Config{}
  136. err = smtpCfg.Initialize("..")
  137. require.NoError(t, err)
  138. err = check.sendEmailNotification(1*time.Second, nil)
  139. assert.Error(t, err)
  140. }
  141. func TestRetentionHookNotifications(t *testing.T) {
  142. dataRetentionHook := Config.DataRetentionHook
  143. Config.DataRetentionHook = fmt.Sprintf("http://%v", httpAddr)
  144. user := dataprovider.User{
  145. BaseUser: sdk.BaseUser{
  146. Username: "user2",
  147. },
  148. }
  149. user.Permissions = make(map[string][]string)
  150. user.Permissions["/"] = []string{dataprovider.PermAny}
  151. check := RetentionCheck{
  152. Notifications: []RetentionCheckNotification{RetentionCheckNotificationHook},
  153. results: []*folderRetentionCheckResult{
  154. {
  155. Path: "/",
  156. Retention: 24,
  157. DeletedFiles: 10,
  158. DeletedSize: 32657,
  159. Elapsed: 10 * time.Second,
  160. },
  161. },
  162. }
  163. conn := NewBaseConnection("", "", "", "", user)
  164. conn.SetProtocol(ProtocolDataRetention)
  165. conn.ID = fmt.Sprintf("data_retention_%v", user.Username)
  166. check.conn = conn
  167. check.sendNotifications(1*time.Second, nil)
  168. err := check.sendHookNotification(1*time.Second, nil)
  169. assert.NoError(t, err)
  170. Config.DataRetentionHook = fmt.Sprintf("http://%v/404", httpAddr)
  171. err = check.sendHookNotification(1*time.Second, nil)
  172. assert.ErrorIs(t, err, errUnexpectedHTTResponse)
  173. Config.DataRetentionHook = "http://foo\x7f.com/retention"
  174. err = check.sendHookNotification(1*time.Second, err)
  175. assert.Error(t, err)
  176. Config.DataRetentionHook = "relativepath"
  177. err = check.sendHookNotification(1*time.Second, err)
  178. assert.Error(t, err)
  179. if runtime.GOOS != osWindows {
  180. hookCmd, err := exec.LookPath("true")
  181. assert.NoError(t, err)
  182. Config.DataRetentionHook = hookCmd
  183. err = check.sendHookNotification(1*time.Second, err)
  184. assert.NoError(t, err)
  185. }
  186. Config.DataRetentionHook = dataRetentionHook
  187. }
  188. func TestRetentionPermissionsAndGetFolder(t *testing.T) {
  189. user := dataprovider.User{
  190. BaseUser: sdk.BaseUser{
  191. Username: "user1",
  192. },
  193. }
  194. user.Permissions = make(map[string][]string)
  195. user.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDelete}
  196. user.Permissions["/dir1"] = []string{dataprovider.PermListItems}
  197. user.Permissions["/dir2/sub1"] = []string{dataprovider.PermCreateDirs}
  198. user.Permissions["/dir2/sub2"] = []string{dataprovider.PermDelete}
  199. check := RetentionCheck{
  200. Folders: []FolderRetention{
  201. {
  202. Path: "/dir2",
  203. Retention: 24 * 7,
  204. IgnoreUserPermissions: true,
  205. },
  206. {
  207. Path: "/dir3",
  208. Retention: 24 * 7,
  209. IgnoreUserPermissions: false,
  210. },
  211. {
  212. Path: "/dir2/sub1/sub",
  213. Retention: 24,
  214. IgnoreUserPermissions: true,
  215. },
  216. },
  217. }
  218. conn := NewBaseConnection("", "", "", "", user)
  219. conn.SetProtocol(ProtocolDataRetention)
  220. conn.ID = fmt.Sprintf("data_retention_%v", user.Username)
  221. check.conn = conn
  222. check.updateUserPermissions()
  223. assert.Equal(t, []string{dataprovider.PermListItems, dataprovider.PermDelete}, conn.User.Permissions["/"])
  224. assert.Equal(t, []string{dataprovider.PermListItems}, conn.User.Permissions["/dir1"])
  225. assert.Equal(t, []string{dataprovider.PermAny}, conn.User.Permissions["/dir2"])
  226. assert.Equal(t, []string{dataprovider.PermAny}, conn.User.Permissions["/dir2/sub1/sub"])
  227. assert.Equal(t, []string{dataprovider.PermCreateDirs}, conn.User.Permissions["/dir2/sub1"])
  228. assert.Equal(t, []string{dataprovider.PermDelete}, conn.User.Permissions["/dir2/sub2"])
  229. _, err := check.getFolderRetention("/")
  230. assert.Error(t, err)
  231. folder, err := check.getFolderRetention("/dir3")
  232. assert.NoError(t, err)
  233. assert.Equal(t, "/dir3", folder.Path)
  234. folder, err = check.getFolderRetention("/dir2/sub3")
  235. assert.NoError(t, err)
  236. assert.Equal(t, "/dir2", folder.Path)
  237. folder, err = check.getFolderRetention("/dir2/sub2")
  238. assert.NoError(t, err)
  239. assert.Equal(t, "/dir2", folder.Path)
  240. folder, err = check.getFolderRetention("/dir2/sub1")
  241. assert.NoError(t, err)
  242. assert.Equal(t, "/dir2", folder.Path)
  243. folder, err = check.getFolderRetention("/dir2/sub1/sub/sub")
  244. assert.NoError(t, err)
  245. assert.Equal(t, "/dir2/sub1/sub", folder.Path)
  246. }
  247. func TestRetentionCheckAddRemove(t *testing.T) {
  248. username := "username"
  249. user := dataprovider.User{
  250. BaseUser: sdk.BaseUser{
  251. Username: username,
  252. },
  253. }
  254. user.Permissions = make(map[string][]string)
  255. user.Permissions["/"] = []string{dataprovider.PermAny}
  256. check := RetentionCheck{
  257. Folders: []FolderRetention{
  258. {
  259. Path: "/",
  260. Retention: 48,
  261. },
  262. },
  263. Notifications: []RetentionCheckNotification{RetentionCheckNotificationHook},
  264. }
  265. assert.NotNil(t, RetentionChecks.Add(check, &user))
  266. checks := RetentionChecks.Get()
  267. require.Len(t, checks, 1)
  268. assert.Equal(t, username, checks[0].Username)
  269. assert.Greater(t, checks[0].StartTime, int64(0))
  270. require.Len(t, checks[0].Folders, 1)
  271. assert.Equal(t, check.Folders[0].Path, checks[0].Folders[0].Path)
  272. assert.Equal(t, check.Folders[0].Retention, checks[0].Folders[0].Retention)
  273. require.Len(t, checks[0].Notifications, 1)
  274. assert.Equal(t, RetentionCheckNotificationHook, checks[0].Notifications[0])
  275. assert.Nil(t, RetentionChecks.Add(check, &user))
  276. assert.True(t, RetentionChecks.remove(username))
  277. require.Len(t, RetentionChecks.Get(), 0)
  278. assert.False(t, RetentionChecks.remove(username))
  279. }
  280. func TestCleanupErrors(t *testing.T) {
  281. user := dataprovider.User{
  282. BaseUser: sdk.BaseUser{
  283. Username: "u",
  284. },
  285. }
  286. user.Permissions = make(map[string][]string)
  287. user.Permissions["/"] = []string{dataprovider.PermAny}
  288. check := &RetentionCheck{
  289. Folders: []FolderRetention{
  290. {
  291. Path: "/path",
  292. Retention: 48,
  293. },
  294. },
  295. }
  296. check = RetentionChecks.Add(*check, &user)
  297. require.NotNil(t, check)
  298. err := check.removeFile("missing file", nil)
  299. assert.Error(t, err)
  300. err = check.cleanupFolder("/")
  301. assert.Error(t, err)
  302. assert.True(t, RetentionChecks.remove(user.Username))
  303. }