eventrule.go 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179
  1. // Copyright (C) 2019-2022 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package dataprovider
  15. import (
  16. "crypto/tls"
  17. "encoding/json"
  18. "fmt"
  19. "net/http"
  20. "path"
  21. "path/filepath"
  22. "strings"
  23. "time"
  24. "github.com/robfig/cron/v3"
  25. "github.com/drakkan/sftpgo/v2/internal/kms"
  26. "github.com/drakkan/sftpgo/v2/internal/logger"
  27. "github.com/drakkan/sftpgo/v2/internal/util"
  28. )
  29. // Supported event actions
  30. const (
  31. ActionTypeHTTP = iota + 1
  32. ActionTypeCommand
  33. ActionTypeEmail
  34. ActionTypeBackup
  35. ActionTypeUserQuotaReset
  36. ActionTypeFolderQuotaReset
  37. ActionTypeTransferQuotaReset
  38. ActionTypeDataRetentionCheck
  39. ActionTypeFilesystem
  40. )
  41. var (
  42. supportedEventActions = []int{ActionTypeHTTP, ActionTypeCommand, ActionTypeEmail, ActionTypeBackup,
  43. ActionTypeUserQuotaReset, ActionTypeFolderQuotaReset, ActionTypeTransferQuotaReset,
  44. ActionTypeDataRetentionCheck, ActionTypeFilesystem}
  45. )
  46. func isActionTypeValid(action int) bool {
  47. return util.Contains(supportedEventActions, action)
  48. }
  49. func getActionTypeAsString(action int) string {
  50. switch action {
  51. case ActionTypeHTTP:
  52. return "HTTP"
  53. case ActionTypeEmail:
  54. return "Email"
  55. case ActionTypeBackup:
  56. return "Backup"
  57. case ActionTypeUserQuotaReset:
  58. return "User quota reset"
  59. case ActionTypeFolderQuotaReset:
  60. return "Folder quota reset"
  61. case ActionTypeTransferQuotaReset:
  62. return "Transfer quota reset"
  63. case ActionTypeDataRetentionCheck:
  64. return "Data retention check"
  65. case ActionTypeFilesystem:
  66. return "Filesystem"
  67. default:
  68. return "Command"
  69. }
  70. }
  71. // Supported event triggers
  72. const (
  73. // Filesystem events such as upload, download, mkdir ...
  74. EventTriggerFsEvent = iota + 1
  75. // Provider events such as add, update, delete
  76. EventTriggerProviderEvent
  77. EventTriggerSchedule
  78. EventTriggerIPBlocked
  79. EventTriggerCertificate
  80. )
  81. var (
  82. supportedEventTriggers = []int{EventTriggerFsEvent, EventTriggerProviderEvent, EventTriggerSchedule,
  83. EventTriggerIPBlocked, EventTriggerCertificate}
  84. )
  85. func isEventTriggerValid(trigger int) bool {
  86. return util.Contains(supportedEventTriggers, trigger)
  87. }
  88. func getTriggerTypeAsString(trigger int) string {
  89. switch trigger {
  90. case EventTriggerFsEvent:
  91. return "Filesystem event"
  92. case EventTriggerProviderEvent:
  93. return "Provider event"
  94. case EventTriggerIPBlocked:
  95. return "IP blocked"
  96. case EventTriggerCertificate:
  97. return "Certificate renewal"
  98. default:
  99. return "Schedule"
  100. }
  101. }
  102. // Supported filesystem actions
  103. const (
  104. FilesystemActionRename = iota + 1
  105. FilesystemActionDelete
  106. FilesystemActionMkdirs
  107. FilesystemActionExist
  108. )
  109. var (
  110. supportedFsActions = []int{FilesystemActionRename, FilesystemActionDelete, FilesystemActionMkdirs,
  111. FilesystemActionExist}
  112. )
  113. func isFilesystemActionValid(value int) bool {
  114. return util.Contains(supportedFsActions, value)
  115. }
  116. func getFsActionTypeAsString(value int) string {
  117. switch value {
  118. case FilesystemActionRename:
  119. return "Rename"
  120. case FilesystemActionDelete:
  121. return "Delete"
  122. case FilesystemActionExist:
  123. return "Paths exist"
  124. default:
  125. return "Create directories"
  126. }
  127. }
  128. // TODO: replace the copied strings with shared constants
  129. var (
  130. // SupportedFsEvents defines the supported filesystem events
  131. SupportedFsEvents = []string{"upload", "first-upload", "download", "first-download", "delete", "rename",
  132. "mkdir", "rmdir", "ssh_cmd"}
  133. // SupportedProviderEvents defines the supported provider events
  134. SupportedProviderEvents = []string{operationAdd, operationUpdate, operationDelete}
  135. // SupportedRuleConditionProtocols defines the supported protcols for rule conditions
  136. SupportedRuleConditionProtocols = []string{"SFTP", "SCP", "SSH", "FTP", "DAV", "HTTP", "HTTPShare",
  137. "OIDC"}
  138. // SupporteRuleConditionProviderObjects defines the supported provider objects for rule conditions
  139. SupporteRuleConditionProviderObjects = []string{actionObjectUser, actionObjectFolder, actionObjectGroup,
  140. actionObjectAdmin, actionObjectAPIKey, actionObjectShare, actionObjectEventRule, actionObjectEventAction}
  141. // SupportedHTTPActionMethods defines the supported methods for HTTP actions
  142. SupportedHTTPActionMethods = []string{http.MethodPost, http.MethodGet, http.MethodPut}
  143. )
  144. // enum mappings
  145. var (
  146. EventActionTypes []EnumMapping
  147. EventTriggerTypes []EnumMapping
  148. FsActionTypes []EnumMapping
  149. )
  150. func init() {
  151. for _, t := range supportedEventActions {
  152. EventActionTypes = append(EventActionTypes, EnumMapping{
  153. Value: t,
  154. Name: getActionTypeAsString(t),
  155. })
  156. }
  157. for _, t := range supportedEventTriggers {
  158. EventTriggerTypes = append(EventTriggerTypes, EnumMapping{
  159. Value: t,
  160. Name: getTriggerTypeAsString(t),
  161. })
  162. }
  163. for _, t := range supportedFsActions {
  164. FsActionTypes = append(FsActionTypes, EnumMapping{
  165. Value: t,
  166. Name: getFsActionTypeAsString(t),
  167. })
  168. }
  169. }
  170. // EnumMapping defines a mapping between enum values and names
  171. type EnumMapping struct {
  172. Name string
  173. Value int
  174. }
  175. // KeyValue defines a key/value pair
  176. type KeyValue struct {
  177. Key string `json:"key"`
  178. Value string `json:"value"`
  179. }
  180. // EventActionHTTPConfig defines the configuration for an HTTP event target
  181. type EventActionHTTPConfig struct {
  182. Endpoint string `json:"endpoint,omitempty"`
  183. Username string `json:"username,omitempty"`
  184. Password *kms.Secret `json:"password,omitempty"`
  185. Headers []KeyValue `json:"headers,omitempty"`
  186. Timeout int `json:"timeout,omitempty"`
  187. SkipTLSVerify bool `json:"skip_tls_verify,omitempty"`
  188. Method string `json:"method,omitempty"`
  189. QueryParameters []KeyValue `json:"query_parameters,omitempty"`
  190. Body string `json:"post_body,omitempty"`
  191. }
  192. func (c *EventActionHTTPConfig) validate(additionalData string) error {
  193. if c.Endpoint == "" {
  194. return util.NewValidationError("HTTP endpoint is required")
  195. }
  196. if !util.IsStringPrefixInSlice(c.Endpoint, []string{"http://", "https://"}) {
  197. return util.NewValidationError("invalid HTTP endpoint schema: http and https are supported")
  198. }
  199. if c.Timeout < 1 || c.Timeout > 120 {
  200. return util.NewValidationError(fmt.Sprintf("invalid HTTP timeout %d", c.Timeout))
  201. }
  202. for _, kv := range c.Headers {
  203. if kv.Key == "" || kv.Value == "" {
  204. return util.NewValidationError("invalid HTTP headers")
  205. }
  206. }
  207. if c.Password.IsRedacted() {
  208. return util.NewValidationError("cannot save HTTP configuration with a redacted secret")
  209. }
  210. if c.Password.IsPlain() {
  211. c.Password.SetAdditionalData(additionalData)
  212. err := c.Password.Encrypt()
  213. if err != nil {
  214. return util.NewValidationError(fmt.Sprintf("could not encrypt HTTP password: %v", err))
  215. }
  216. }
  217. if !util.Contains(SupportedHTTPActionMethods, c.Method) {
  218. return util.NewValidationError(fmt.Sprintf("unsupported HTTP method: %s", c.Method))
  219. }
  220. for _, kv := range c.QueryParameters {
  221. if kv.Key == "" || kv.Value == "" {
  222. return util.NewValidationError("invalid HTTP query parameters")
  223. }
  224. }
  225. return nil
  226. }
  227. // GetHTTPClient returns an HTTP client based on the config
  228. func (c *EventActionHTTPConfig) GetHTTPClient() *http.Client {
  229. client := &http.Client{
  230. Timeout: time.Duration(c.Timeout) * time.Second,
  231. }
  232. if c.SkipTLSVerify {
  233. transport := http.DefaultTransport.(*http.Transport).Clone()
  234. if transport.TLSClientConfig != nil {
  235. transport.TLSClientConfig.InsecureSkipVerify = true
  236. } else {
  237. transport.TLSClientConfig = &tls.Config{
  238. NextProtos: []string{"http/1.1", "h2"},
  239. InsecureSkipVerify: true,
  240. }
  241. }
  242. client.Transport = transport
  243. }
  244. return client
  245. }
  246. // EventActionCommandConfig defines the configuration for a command event target
  247. type EventActionCommandConfig struct {
  248. Cmd string `json:"cmd,omitempty"`
  249. Timeout int `json:"timeout,omitempty"`
  250. EnvVars []KeyValue `json:"env_vars,omitempty"`
  251. }
  252. func (c *EventActionCommandConfig) validate() error {
  253. if c.Cmd == "" {
  254. return util.NewValidationError("command is required")
  255. }
  256. if !filepath.IsAbs(c.Cmd) {
  257. return util.NewValidationError("invalid command, it must be an absolute path")
  258. }
  259. if c.Timeout < 1 || c.Timeout > 120 {
  260. return util.NewValidationError(fmt.Sprintf("invalid command action timeout %d", c.Timeout))
  261. }
  262. for _, kv := range c.EnvVars {
  263. if kv.Key == "" || kv.Value == "" {
  264. return util.NewValidationError("invalid command env vars")
  265. }
  266. }
  267. return nil
  268. }
  269. // EventActionEmailConfig defines the configuration options for SMTP event actions
  270. type EventActionEmailConfig struct {
  271. Recipients []string `json:"recipients,omitempty"`
  272. Subject string `json:"subject,omitempty"`
  273. Body string `json:"body,omitempty"`
  274. }
  275. // GetRecipientsAsString returns the list of recipients as comma separated string
  276. func (c EventActionEmailConfig) GetRecipientsAsString() string {
  277. return strings.Join(c.Recipients, ",")
  278. }
  279. func (c *EventActionEmailConfig) validate() error {
  280. if len(c.Recipients) == 0 {
  281. return util.NewValidationError("at least one email recipient is required")
  282. }
  283. c.Recipients = util.RemoveDuplicates(c.Recipients, false)
  284. for _, r := range c.Recipients {
  285. if r == "" {
  286. return util.NewValidationError("invalid email recipients")
  287. }
  288. }
  289. if c.Subject == "" {
  290. return util.NewValidationError("email subject is required")
  291. }
  292. if c.Body == "" {
  293. return util.NewValidationError("email body is required")
  294. }
  295. return nil
  296. }
  297. // FolderRetention defines a folder retention configuration
  298. type FolderRetention struct {
  299. // Path is the exposed virtual directory path, if no other specific retention is defined,
  300. // the retention applies for sub directories too. For example if retention is defined
  301. // for the paths "/" and "/sub" then the retention for "/" is applied for any file outside
  302. // the "/sub" directory
  303. Path string `json:"path"`
  304. // Retention time in hours. 0 means exclude this path
  305. Retention int `json:"retention"`
  306. // DeleteEmptyDirs defines if empty directories will be deleted.
  307. // The user need the delete permission
  308. DeleteEmptyDirs bool `json:"delete_empty_dirs,omitempty"`
  309. // IgnoreUserPermissions defines whether to delete files even if the user does not have the delete permission.
  310. // The default is "false" which means that files will be skipped if the user does not have the permission
  311. // to delete them. This applies to sub directories too.
  312. IgnoreUserPermissions bool `json:"ignore_user_permissions,omitempty"`
  313. }
  314. // Validate returns an error if the configuration is not valid
  315. func (f *FolderRetention) Validate() error {
  316. f.Path = util.CleanPath(f.Path)
  317. if f.Retention < 0 {
  318. return util.NewValidationError(fmt.Sprintf("invalid folder retention %v, it must be greater or equal to zero",
  319. f.Retention))
  320. }
  321. return nil
  322. }
  323. // EventActionDataRetentionConfig defines the configuration for a data retention check
  324. type EventActionDataRetentionConfig struct {
  325. Folders []FolderRetention `json:"folders,omitempty"`
  326. }
  327. func (c *EventActionDataRetentionConfig) validate() error {
  328. folderPaths := make(map[string]bool)
  329. nothingToDo := true
  330. for idx := range c.Folders {
  331. f := &c.Folders[idx]
  332. if err := f.Validate(); err != nil {
  333. return err
  334. }
  335. if f.Retention > 0 {
  336. nothingToDo = false
  337. }
  338. if _, ok := folderPaths[f.Path]; ok {
  339. return util.NewValidationError(fmt.Sprintf("duplicated folder path %#v", f.Path))
  340. }
  341. folderPaths[f.Path] = true
  342. }
  343. if nothingToDo {
  344. return util.NewValidationError("nothing to delete!")
  345. }
  346. return nil
  347. }
  348. // EventActionFilesystemConfig defines the configuration for filesystem actions
  349. type EventActionFilesystemConfig struct {
  350. // Filesystem actions, see the above enum
  351. Type int `json:"type,omitempty"`
  352. // files/dirs to rename, key is the source and target the value
  353. Renames []KeyValue `json:"renames,omitempty"`
  354. // directories to create
  355. MkDirs []string `json:"mkdirs,omitempty"`
  356. // files/dirs to delete
  357. Deletes []string `json:"deletes,omitempty"`
  358. // file/dirs to check for existence
  359. Exist []string `json:"exist,omitempty"`
  360. }
  361. // GetDeletesAsString returns the list of items to delete as comma separated string.
  362. // Using a pointer receiver will not work in web templates
  363. func (c EventActionFilesystemConfig) GetDeletesAsString() string {
  364. return strings.Join(c.Deletes, ",")
  365. }
  366. // GetMkDirsAsString returns the list of directories to create as comma separated string.
  367. // Using a pointer receiver will not work in web templates
  368. func (c EventActionFilesystemConfig) GetMkDirsAsString() string {
  369. return strings.Join(c.MkDirs, ",")
  370. }
  371. // GetExistAsString returns the list of items to check for existence as comma separated string.
  372. // Using a pointer receiver will not work in web templates
  373. func (c EventActionFilesystemConfig) GetExistAsString() string {
  374. return strings.Join(c.Exist, ",")
  375. }
  376. func (c *EventActionFilesystemConfig) validateRenames() error {
  377. if len(c.Renames) == 0 {
  378. return util.NewValidationError("no path to rename specified")
  379. }
  380. for idx, kv := range c.Renames {
  381. key := strings.TrimSpace(kv.Key)
  382. value := strings.TrimSpace(kv.Value)
  383. if key == "" || value == "" {
  384. return util.NewValidationError("invalid paths to rename")
  385. }
  386. key = util.CleanPath(key)
  387. value = util.CleanPath(value)
  388. if key == value {
  389. return util.NewValidationError("rename source and target cannot be equal")
  390. }
  391. if key == "/" || value == "/" {
  392. return util.NewValidationError("renaming the root directory is not allowed")
  393. }
  394. c.Renames[idx] = KeyValue{
  395. Key: key,
  396. Value: value,
  397. }
  398. }
  399. return nil
  400. }
  401. func (c *EventActionFilesystemConfig) validateDeletes() error {
  402. if len(c.Deletes) == 0 {
  403. return util.NewValidationError("no path to delete specified")
  404. }
  405. for idx, val := range c.Deletes {
  406. val = strings.TrimSpace(val)
  407. if val == "" {
  408. return util.NewValidationError("invalid path to delete")
  409. }
  410. c.Deletes[idx] = util.CleanPath(val)
  411. }
  412. c.Deletes = util.RemoveDuplicates(c.Deletes, false)
  413. return nil
  414. }
  415. func (c *EventActionFilesystemConfig) validateMkdirs() error {
  416. if len(c.MkDirs) == 0 {
  417. return util.NewValidationError("no directory to create specified")
  418. }
  419. for idx, val := range c.MkDirs {
  420. val = strings.TrimSpace(val)
  421. if val == "" {
  422. return util.NewValidationError("invalid directory to create")
  423. }
  424. c.MkDirs[idx] = util.CleanPath(val)
  425. }
  426. c.MkDirs = util.RemoveDuplicates(c.MkDirs, false)
  427. return nil
  428. }
  429. func (c *EventActionFilesystemConfig) validateExist() error {
  430. if len(c.Exist) == 0 {
  431. return util.NewValidationError("no path to check for existence specified")
  432. }
  433. for idx, val := range c.Exist {
  434. val = strings.TrimSpace(val)
  435. if val == "" {
  436. return util.NewValidationError("invalid path to check for existence")
  437. }
  438. c.Exist[idx] = util.CleanPath(val)
  439. }
  440. c.Exist = util.RemoveDuplicates(c.Exist, false)
  441. return nil
  442. }
  443. func (c *EventActionFilesystemConfig) validate() error {
  444. if !isFilesystemActionValid(c.Type) {
  445. return util.NewValidationError(fmt.Sprintf("invalid filesystem action type: %d", c.Type))
  446. }
  447. switch c.Type {
  448. case FilesystemActionRename:
  449. c.MkDirs = nil
  450. c.Deletes = nil
  451. c.Exist = nil
  452. if err := c.validateRenames(); err != nil {
  453. return err
  454. }
  455. case FilesystemActionDelete:
  456. c.Renames = nil
  457. c.MkDirs = nil
  458. c.Exist = nil
  459. if err := c.validateDeletes(); err != nil {
  460. return err
  461. }
  462. case FilesystemActionMkdirs:
  463. c.Renames = nil
  464. c.Deletes = nil
  465. c.Exist = nil
  466. if err := c.validateMkdirs(); err != nil {
  467. return err
  468. }
  469. case FilesystemActionExist:
  470. c.Renames = nil
  471. c.Deletes = nil
  472. c.MkDirs = nil
  473. if err := c.validateExist(); err != nil {
  474. return err
  475. }
  476. }
  477. return nil
  478. }
  479. func (c *EventActionFilesystemConfig) getACopy() EventActionFilesystemConfig {
  480. mkdirs := make([]string, len(c.MkDirs))
  481. copy(mkdirs, c.MkDirs)
  482. deletes := make([]string, len(c.Deletes))
  483. copy(deletes, c.Deletes)
  484. exist := make([]string, len(c.Exist))
  485. copy(exist, c.Exist)
  486. return EventActionFilesystemConfig{
  487. Type: c.Type,
  488. Renames: cloneKeyValues(c.Renames),
  489. MkDirs: mkdirs,
  490. Deletes: deletes,
  491. Exist: exist,
  492. }
  493. }
  494. // BaseEventActionOptions defines the supported configuration options for a base event actions
  495. type BaseEventActionOptions struct {
  496. HTTPConfig EventActionHTTPConfig `json:"http_config"`
  497. CmdConfig EventActionCommandConfig `json:"cmd_config"`
  498. EmailConfig EventActionEmailConfig `json:"email_config"`
  499. RetentionConfig EventActionDataRetentionConfig `json:"retention_config"`
  500. FsConfig EventActionFilesystemConfig `json:"fs_config"`
  501. }
  502. func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions {
  503. o.SetEmptySecretsIfNil()
  504. emailRecipients := make([]string, len(o.EmailConfig.Recipients))
  505. copy(emailRecipients, o.EmailConfig.Recipients)
  506. folders := make([]FolderRetention, 0, len(o.RetentionConfig.Folders))
  507. for _, folder := range o.RetentionConfig.Folders {
  508. folders = append(folders, FolderRetention{
  509. Path: folder.Path,
  510. Retention: folder.Retention,
  511. DeleteEmptyDirs: folder.DeleteEmptyDirs,
  512. IgnoreUserPermissions: folder.IgnoreUserPermissions,
  513. })
  514. }
  515. return BaseEventActionOptions{
  516. HTTPConfig: EventActionHTTPConfig{
  517. Endpoint: o.HTTPConfig.Endpoint,
  518. Username: o.HTTPConfig.Username,
  519. Password: o.HTTPConfig.Password.Clone(),
  520. Headers: cloneKeyValues(o.HTTPConfig.Headers),
  521. Timeout: o.HTTPConfig.Timeout,
  522. SkipTLSVerify: o.HTTPConfig.SkipTLSVerify,
  523. Method: o.HTTPConfig.Method,
  524. QueryParameters: cloneKeyValues(o.HTTPConfig.QueryParameters),
  525. Body: o.HTTPConfig.Body,
  526. },
  527. CmdConfig: EventActionCommandConfig{
  528. Cmd: o.CmdConfig.Cmd,
  529. Timeout: o.CmdConfig.Timeout,
  530. EnvVars: cloneKeyValues(o.CmdConfig.EnvVars),
  531. },
  532. EmailConfig: EventActionEmailConfig{
  533. Recipients: emailRecipients,
  534. Subject: o.EmailConfig.Subject,
  535. Body: o.EmailConfig.Body,
  536. },
  537. RetentionConfig: EventActionDataRetentionConfig{
  538. Folders: folders,
  539. },
  540. FsConfig: o.FsConfig.getACopy(),
  541. }
  542. }
  543. // SetEmptySecretsIfNil sets the secrets to empty if nil
  544. func (o *BaseEventActionOptions) SetEmptySecretsIfNil() {
  545. if o.HTTPConfig.Password == nil {
  546. o.HTTPConfig.Password = kms.NewEmptySecret()
  547. }
  548. }
  549. func (o *BaseEventActionOptions) setNilSecretsIfEmpty() {
  550. if o.HTTPConfig.Password != nil && o.HTTPConfig.Password.IsEmpty() {
  551. o.HTTPConfig.Password = nil
  552. }
  553. }
  554. func (o *BaseEventActionOptions) hideConfidentialData() {
  555. if o.HTTPConfig.Password != nil {
  556. o.HTTPConfig.Password.Hide()
  557. }
  558. }
  559. func (o *BaseEventActionOptions) validate(action int, name string) error {
  560. o.SetEmptySecretsIfNil()
  561. switch action {
  562. case ActionTypeHTTP:
  563. o.CmdConfig = EventActionCommandConfig{}
  564. o.EmailConfig = EventActionEmailConfig{}
  565. o.RetentionConfig = EventActionDataRetentionConfig{}
  566. o.FsConfig = EventActionFilesystemConfig{}
  567. return o.HTTPConfig.validate(name)
  568. case ActionTypeCommand:
  569. o.HTTPConfig = EventActionHTTPConfig{}
  570. o.EmailConfig = EventActionEmailConfig{}
  571. o.RetentionConfig = EventActionDataRetentionConfig{}
  572. o.FsConfig = EventActionFilesystemConfig{}
  573. return o.CmdConfig.validate()
  574. case ActionTypeEmail:
  575. o.HTTPConfig = EventActionHTTPConfig{}
  576. o.CmdConfig = EventActionCommandConfig{}
  577. o.RetentionConfig = EventActionDataRetentionConfig{}
  578. o.FsConfig = EventActionFilesystemConfig{}
  579. return o.EmailConfig.validate()
  580. case ActionTypeDataRetentionCheck:
  581. o.HTTPConfig = EventActionHTTPConfig{}
  582. o.CmdConfig = EventActionCommandConfig{}
  583. o.EmailConfig = EventActionEmailConfig{}
  584. o.FsConfig = EventActionFilesystemConfig{}
  585. return o.RetentionConfig.validate()
  586. case ActionTypeFilesystem:
  587. o.HTTPConfig = EventActionHTTPConfig{}
  588. o.CmdConfig = EventActionCommandConfig{}
  589. o.EmailConfig = EventActionEmailConfig{}
  590. o.RetentionConfig = EventActionDataRetentionConfig{}
  591. return o.FsConfig.validate()
  592. default:
  593. o.HTTPConfig = EventActionHTTPConfig{}
  594. o.CmdConfig = EventActionCommandConfig{}
  595. o.EmailConfig = EventActionEmailConfig{}
  596. o.RetentionConfig = EventActionDataRetentionConfig{}
  597. o.FsConfig = EventActionFilesystemConfig{}
  598. }
  599. return nil
  600. }
  601. // BaseEventAction defines the common fields for an event action
  602. type BaseEventAction struct {
  603. // Data provider unique identifier
  604. ID int64 `json:"id"`
  605. // Action name
  606. Name string `json:"name"`
  607. // optional description
  608. Description string `json:"description,omitempty"`
  609. // ActionType, see the above enum
  610. Type int `json:"type"`
  611. // Configuration options specific for the action type
  612. Options BaseEventActionOptions `json:"options"`
  613. // list of rule names associated with this event action
  614. Rules []string `json:"rules,omitempty"`
  615. }
  616. func (a *BaseEventAction) getACopy() BaseEventAction {
  617. rules := make([]string, len(a.Rules))
  618. copy(rules, a.Rules)
  619. return BaseEventAction{
  620. ID: a.ID,
  621. Name: a.Name,
  622. Description: a.Description,
  623. Type: a.Type,
  624. Options: a.Options.getACopy(),
  625. Rules: rules,
  626. }
  627. }
  628. // GetTypeAsString returns the action type as string
  629. func (a *BaseEventAction) GetTypeAsString() string {
  630. return getActionTypeAsString(a.Type)
  631. }
  632. // GetRulesAsString returns the list of rules as comma separated string
  633. func (a *BaseEventAction) GetRulesAsString() string {
  634. return strings.Join(a.Rules, ",")
  635. }
  636. // PrepareForRendering prepares a BaseEventAction for rendering.
  637. // It hides confidential data and set to nil the empty secrets
  638. // so they are not serialized
  639. func (a *BaseEventAction) PrepareForRendering() {
  640. a.Options.setNilSecretsIfEmpty()
  641. a.Options.hideConfidentialData()
  642. }
  643. // RenderAsJSON implements the renderer interface used within plugins
  644. func (a *BaseEventAction) RenderAsJSON(reload bool) ([]byte, error) {
  645. if reload {
  646. action, err := provider.eventActionExists(a.Name)
  647. if err != nil {
  648. providerLog(logger.LevelError, "unable to reload event action before rendering as json: %v", err)
  649. return nil, err
  650. }
  651. action.PrepareForRendering()
  652. return json.Marshal(action)
  653. }
  654. a.PrepareForRendering()
  655. return json.Marshal(a)
  656. }
  657. func (a *BaseEventAction) validate() error {
  658. if a.Name == "" {
  659. return util.NewValidationError("name is mandatory")
  660. }
  661. if !isActionTypeValid(a.Type) {
  662. return util.NewValidationError(fmt.Sprintf("invalid action type: %d", a.Type))
  663. }
  664. return a.Options.validate(a.Type, a.Name)
  665. }
  666. // EventActionOptions defines the supported configuration options for an event action
  667. type EventActionOptions struct {
  668. IsFailureAction bool `json:"is_failure_action"`
  669. StopOnFailure bool `json:"stop_on_failure"`
  670. ExecuteSync bool `json:"execute_sync"`
  671. }
  672. // EventAction defines an event action
  673. type EventAction struct {
  674. BaseEventAction
  675. // Order defines the execution order
  676. Order int `json:"order,omitempty"`
  677. Options EventActionOptions `json:"relation_options"`
  678. }
  679. func (a *EventAction) getACopy() EventAction {
  680. return EventAction{
  681. BaseEventAction: a.BaseEventAction.getACopy(),
  682. Order: a.Order,
  683. Options: EventActionOptions{
  684. IsFailureAction: a.Options.IsFailureAction,
  685. StopOnFailure: a.Options.StopOnFailure,
  686. ExecuteSync: a.Options.ExecuteSync,
  687. },
  688. }
  689. }
  690. func (a *EventAction) validateAssociation(trigger int, fsEvents []string) error {
  691. if a.Options.IsFailureAction {
  692. if a.Options.ExecuteSync {
  693. return util.NewValidationError("sync execution is not supported for failure actions")
  694. }
  695. }
  696. if trigger != EventTriggerFsEvent || !util.Contains(fsEvents, "upload") {
  697. if a.Options.ExecuteSync {
  698. return util.NewValidationError("sync execution is only supported for upload event")
  699. }
  700. }
  701. return nil
  702. }
  703. // ConditionPattern defines a pattern for condition filters
  704. type ConditionPattern struct {
  705. Pattern string `json:"pattern,omitempty"`
  706. InverseMatch bool `json:"inverse_match,omitempty"`
  707. }
  708. func (p *ConditionPattern) validate() error {
  709. if p.Pattern == "" {
  710. return util.NewValidationError("empty condition pattern not allowed")
  711. }
  712. _, err := path.Match(p.Pattern, "abc")
  713. if err != nil {
  714. return util.NewValidationError(fmt.Sprintf("invalid condition pattern %q", p.Pattern))
  715. }
  716. return nil
  717. }
  718. // ConditionOptions defines options for event conditions
  719. type ConditionOptions struct {
  720. // Usernames or folder names
  721. Names []ConditionPattern `json:"names,omitempty"`
  722. // Virtual paths
  723. FsPaths []ConditionPattern `json:"fs_paths,omitempty"`
  724. Protocols []string `json:"protocols,omitempty"`
  725. ProviderObjects []string `json:"provider_objects,omitempty"`
  726. MinFileSize int64 `json:"min_size,omitempty"`
  727. MaxFileSize int64 `json:"max_size,omitempty"`
  728. // allow to execute scheduled tasks concurrently from multiple instances
  729. ConcurrentExecution bool `json:"concurrent_execution,omitempty"`
  730. }
  731. func (f *ConditionOptions) getACopy() ConditionOptions {
  732. protocols := make([]string, len(f.Protocols))
  733. copy(protocols, f.Protocols)
  734. providerObjects := make([]string, len(f.ProviderObjects))
  735. copy(providerObjects, f.ProviderObjects)
  736. return ConditionOptions{
  737. Names: cloneConditionPatterns(f.Names),
  738. FsPaths: cloneConditionPatterns(f.FsPaths),
  739. Protocols: protocols,
  740. ProviderObjects: providerObjects,
  741. MinFileSize: f.MinFileSize,
  742. MaxFileSize: f.MaxFileSize,
  743. ConcurrentExecution: f.ConcurrentExecution,
  744. }
  745. }
  746. func (f *ConditionOptions) validate() error {
  747. for _, name := range f.Names {
  748. if err := name.validate(); err != nil {
  749. return err
  750. }
  751. }
  752. for _, fsPath := range f.FsPaths {
  753. if err := fsPath.validate(); err != nil {
  754. return err
  755. }
  756. }
  757. for _, p := range f.Protocols {
  758. if !util.Contains(SupportedRuleConditionProtocols, p) {
  759. return util.NewValidationError(fmt.Sprintf("unsupported rule condition protocol: %q", p))
  760. }
  761. }
  762. for _, p := range f.ProviderObjects {
  763. if !util.Contains(SupporteRuleConditionProviderObjects, p) {
  764. return util.NewValidationError(fmt.Sprintf("unsupported provider object: %q", p))
  765. }
  766. }
  767. if f.MinFileSize > 0 && f.MaxFileSize > 0 {
  768. if f.MaxFileSize <= f.MinFileSize {
  769. return util.NewValidationError(fmt.Sprintf("invalid max file size %d, it is lesser or equal than min file size %d",
  770. f.MaxFileSize, f.MinFileSize))
  771. }
  772. }
  773. if config.IsShared == 0 {
  774. f.ConcurrentExecution = false
  775. }
  776. return nil
  777. }
  778. // Schedule defines an event schedule
  779. type Schedule struct {
  780. Hours string `json:"hour"`
  781. DayOfWeek string `json:"day_of_week"`
  782. DayOfMonth string `json:"day_of_month"`
  783. Month string `json:"month"`
  784. }
  785. // GetCronSpec returns the cron compatible schedule string
  786. func (s *Schedule) GetCronSpec() string {
  787. return fmt.Sprintf("0 %s %s %s %s", s.Hours, s.DayOfMonth, s.Month, s.DayOfWeek)
  788. }
  789. func (s *Schedule) validate() error {
  790. _, err := cron.ParseStandard(s.GetCronSpec())
  791. if err != nil {
  792. return util.NewValidationError(fmt.Sprintf("invalid schedule, hour: %q, day of month: %q, month: %q, day of week: %q",
  793. s.Hours, s.DayOfMonth, s.Month, s.DayOfWeek))
  794. }
  795. return nil
  796. }
  797. // EventConditions defines the conditions for an event rule
  798. type EventConditions struct {
  799. // Only one between FsEvents, ProviderEvents and Schedule is allowed
  800. FsEvents []string `json:"fs_events,omitempty"`
  801. ProviderEvents []string `json:"provider_events,omitempty"`
  802. Schedules []Schedule `json:"schedules,omitempty"`
  803. Options ConditionOptions `json:"options"`
  804. }
  805. func (c *EventConditions) getACopy() EventConditions {
  806. fsEvents := make([]string, len(c.FsEvents))
  807. copy(fsEvents, c.FsEvents)
  808. providerEvents := make([]string, len(c.ProviderEvents))
  809. copy(providerEvents, c.ProviderEvents)
  810. schedules := make([]Schedule, 0, len(c.Schedules))
  811. for _, schedule := range c.Schedules {
  812. schedules = append(schedules, Schedule{
  813. Hours: schedule.Hours,
  814. DayOfWeek: schedule.DayOfWeek,
  815. DayOfMonth: schedule.DayOfMonth,
  816. Month: schedule.Month,
  817. })
  818. }
  819. return EventConditions{
  820. FsEvents: fsEvents,
  821. ProviderEvents: providerEvents,
  822. Schedules: schedules,
  823. Options: c.Options.getACopy(),
  824. }
  825. }
  826. func (c *EventConditions) validate(trigger int) error {
  827. switch trigger {
  828. case EventTriggerFsEvent:
  829. c.ProviderEvents = nil
  830. c.Schedules = nil
  831. c.Options.ProviderObjects = nil
  832. if len(c.FsEvents) == 0 {
  833. return util.NewValidationError("at least one filesystem event is required")
  834. }
  835. for _, ev := range c.FsEvents {
  836. if !util.Contains(SupportedFsEvents, ev) {
  837. return util.NewValidationError(fmt.Sprintf("unsupported fs event: %q", ev))
  838. }
  839. }
  840. case EventTriggerProviderEvent:
  841. c.FsEvents = nil
  842. c.Schedules = nil
  843. c.Options.FsPaths = nil
  844. c.Options.Protocols = nil
  845. c.Options.MinFileSize = 0
  846. c.Options.MaxFileSize = 0
  847. if len(c.ProviderEvents) == 0 {
  848. return util.NewValidationError("at least one provider event is required")
  849. }
  850. for _, ev := range c.ProviderEvents {
  851. if !util.Contains(SupportedProviderEvents, ev) {
  852. return util.NewValidationError(fmt.Sprintf("unsupported provider event: %q", ev))
  853. }
  854. }
  855. case EventTriggerSchedule:
  856. c.FsEvents = nil
  857. c.ProviderEvents = nil
  858. c.Options.FsPaths = nil
  859. c.Options.Protocols = nil
  860. c.Options.MinFileSize = 0
  861. c.Options.MaxFileSize = 0
  862. c.Options.ProviderObjects = nil
  863. if len(c.Schedules) == 0 {
  864. return util.NewValidationError("at least one schedule is required")
  865. }
  866. for _, schedule := range c.Schedules {
  867. if err := schedule.validate(); err != nil {
  868. return err
  869. }
  870. }
  871. case EventTriggerIPBlocked, EventTriggerCertificate:
  872. c.FsEvents = nil
  873. c.ProviderEvents = nil
  874. c.Options.Names = nil
  875. c.Options.FsPaths = nil
  876. c.Options.Protocols = nil
  877. c.Options.MinFileSize = 0
  878. c.Options.MaxFileSize = 0
  879. c.Schedules = nil
  880. default:
  881. c.FsEvents = nil
  882. c.ProviderEvents = nil
  883. c.Options.FsPaths = nil
  884. c.Options.Protocols = nil
  885. c.Options.MinFileSize = 0
  886. c.Options.MaxFileSize = 0
  887. c.Schedules = nil
  888. }
  889. return c.Options.validate()
  890. }
  891. // EventRule defines the trigger, conditions and actions for an event
  892. type EventRule struct {
  893. // Data provider unique identifier
  894. ID int64 `json:"id"`
  895. // Rule name
  896. Name string `json:"name"`
  897. // optional description
  898. Description string `json:"description,omitempty"`
  899. // Creation time as unix timestamp in milliseconds
  900. CreatedAt int64 `json:"created_at"`
  901. // last update time as unix timestamp in milliseconds
  902. UpdatedAt int64 `json:"updated_at"`
  903. // Event trigger
  904. Trigger int `json:"trigger"`
  905. // Event conditions
  906. Conditions EventConditions `json:"conditions"`
  907. // actions to execute
  908. Actions []EventAction `json:"actions"`
  909. // in multi node setups we mark the rule as deleted to be able to update the cache
  910. DeletedAt int64 `json:"-"`
  911. }
  912. func (r *EventRule) getACopy() EventRule {
  913. actions := make([]EventAction, 0, len(r.Actions))
  914. for _, action := range r.Actions {
  915. actions = append(actions, action.getACopy())
  916. }
  917. return EventRule{
  918. ID: r.ID,
  919. Name: r.Name,
  920. Description: r.Description,
  921. CreatedAt: r.CreatedAt,
  922. UpdatedAt: r.UpdatedAt,
  923. Trigger: r.Trigger,
  924. Conditions: r.Conditions.getACopy(),
  925. Actions: actions,
  926. DeletedAt: r.DeletedAt,
  927. }
  928. }
  929. // GuardFromConcurrentExecution returns true if the rule cannot be executed concurrently
  930. // from multiple instances
  931. func (r *EventRule) GuardFromConcurrentExecution() bool {
  932. if config.IsShared == 0 {
  933. return false
  934. }
  935. return !r.Conditions.Options.ConcurrentExecution
  936. }
  937. // GetTriggerAsString returns the rule trigger as string
  938. func (r *EventRule) GetTriggerAsString() string {
  939. return getTriggerTypeAsString(r.Trigger)
  940. }
  941. // GetActionsAsString returns the list of action names as comma separated string
  942. func (r *EventRule) GetActionsAsString() string {
  943. actions := make([]string, 0, len(r.Actions))
  944. for _, action := range r.Actions {
  945. actions = append(actions, action.Name)
  946. }
  947. return strings.Join(actions, ",")
  948. }
  949. func (r *EventRule) validate() error {
  950. if r.Name == "" {
  951. return util.NewValidationError("name is mandatory")
  952. }
  953. if !isEventTriggerValid(r.Trigger) {
  954. return util.NewValidationError(fmt.Sprintf("invalid event rule trigger: %d", r.Trigger))
  955. }
  956. if err := r.Conditions.validate(r.Trigger); err != nil {
  957. return err
  958. }
  959. if len(r.Actions) == 0 {
  960. return util.NewValidationError("at least one action is required")
  961. }
  962. actionNames := make(map[string]bool)
  963. actionOrders := make(map[int]bool)
  964. failureActions := 0
  965. for idx := range r.Actions {
  966. if r.Actions[idx].Name == "" {
  967. return util.NewValidationError(fmt.Sprintf("invalid action at position %d, name not specified", idx))
  968. }
  969. if actionNames[r.Actions[idx].Name] {
  970. return util.NewValidationError(fmt.Sprintf("duplicated action %q", r.Actions[idx].Name))
  971. }
  972. if actionOrders[r.Actions[idx].Order] {
  973. return util.NewValidationError(fmt.Sprintf("duplicated order %d for action %q",
  974. r.Actions[idx].Order, r.Actions[idx].Name))
  975. }
  976. if err := r.Actions[idx].validateAssociation(r.Trigger, r.Conditions.FsEvents); err != nil {
  977. return err
  978. }
  979. if r.Actions[idx].Options.IsFailureAction {
  980. failureActions++
  981. }
  982. actionNames[r.Actions[idx].Name] = true
  983. actionOrders[r.Actions[idx].Order] = true
  984. }
  985. if len(r.Actions) == failureActions {
  986. return util.NewValidationError("at least a non-failure action is required")
  987. }
  988. return nil
  989. }
  990. func (r *EventRule) checkIPBlockedAndCertificateActions() error {
  991. unavailableActions := []int{ActionTypeUserQuotaReset, ActionTypeFolderQuotaReset, ActionTypeTransferQuotaReset,
  992. ActionTypeDataRetentionCheck, ActionTypeFilesystem}
  993. for _, action := range r.Actions {
  994. if util.Contains(unavailableActions, action.Type) {
  995. return fmt.Errorf("action %q, type %q is not supported for event trigger %q",
  996. action.Name, getActionTypeAsString(action.Type), getTriggerTypeAsString(r.Trigger))
  997. }
  998. }
  999. return nil
  1000. }
  1001. func (r *EventRule) checkProviderEventActions(providerObjectType string) error {
  1002. // user quota reset, transfer quota reset, data retention check and filesystem actions
  1003. // can be executed only if we modify a user. They will be executed for the
  1004. // affected user. Folder quota reset can be executed only for folders.
  1005. userSpecificActions := []int{ActionTypeUserQuotaReset, ActionTypeTransferQuotaReset,
  1006. ActionTypeDataRetentionCheck, ActionTypeFilesystem}
  1007. for _, action := range r.Actions {
  1008. if util.Contains(userSpecificActions, action.Type) && providerObjectType != actionObjectUser {
  1009. return fmt.Errorf("action %q, type %q is only supported for provider user events",
  1010. action.Name, getActionTypeAsString(action.Type))
  1011. }
  1012. if action.Type == ActionTypeFolderQuotaReset && providerObjectType != actionObjectFolder {
  1013. return fmt.Errorf("action %q, type %q is only supported for provider folder events",
  1014. action.Name, getActionTypeAsString(action.Type))
  1015. }
  1016. }
  1017. return nil
  1018. }
  1019. // CheckActionsConsistency returns an error if the actions cannot be executed
  1020. func (r *EventRule) CheckActionsConsistency(providerObjectType string) error {
  1021. switch r.Trigger {
  1022. case EventTriggerProviderEvent:
  1023. if err := r.checkProviderEventActions(providerObjectType); err != nil {
  1024. return err
  1025. }
  1026. case EventTriggerFsEvent:
  1027. // folder quota reset cannot be executed
  1028. for _, action := range r.Actions {
  1029. if action.Type == ActionTypeFolderQuotaReset {
  1030. return fmt.Errorf("action %q, type %q is not supported for filesystem events",
  1031. action.Name, getActionTypeAsString(action.Type))
  1032. }
  1033. }
  1034. case EventTriggerIPBlocked, EventTriggerCertificate:
  1035. if err := r.checkIPBlockedAndCertificateActions(); err != nil {
  1036. return err
  1037. }
  1038. }
  1039. return nil
  1040. }
  1041. // PrepareForRendering prepares an EventRule for rendering.
  1042. // It hides confidential data and set to nil the empty secrets
  1043. // so they are not serialized
  1044. func (r *EventRule) PrepareForRendering() {
  1045. for idx := range r.Actions {
  1046. r.Actions[idx].PrepareForRendering()
  1047. }
  1048. }
  1049. // RenderAsJSON implements the renderer interface used within plugins
  1050. func (r *EventRule) RenderAsJSON(reload bool) ([]byte, error) {
  1051. if reload {
  1052. rule, err := provider.eventRuleExists(r.Name)
  1053. if err != nil {
  1054. providerLog(logger.LevelError, "unable to reload event rule before rendering as json: %v", err)
  1055. return nil, err
  1056. }
  1057. rule.PrepareForRendering()
  1058. return json.Marshal(rule)
  1059. }
  1060. r.PrepareForRendering()
  1061. return json.Marshal(r)
  1062. }
  1063. func cloneKeyValues(keyVals []KeyValue) []KeyValue {
  1064. res := make([]KeyValue, 0, len(keyVals))
  1065. for _, kv := range keyVals {
  1066. res = append(res, KeyValue{
  1067. Key: kv.Key,
  1068. Value: kv.Value,
  1069. })
  1070. }
  1071. return res
  1072. }
  1073. func cloneConditionPatterns(patterns []ConditionPattern) []ConditionPattern {
  1074. res := make([]ConditionPattern, 0, len(patterns))
  1075. for _, p := range patterns {
  1076. res = append(res, ConditionPattern{
  1077. Pattern: p.Pattern,
  1078. InverseMatch: p.InverseMatch,
  1079. })
  1080. }
  1081. return res
  1082. }
  1083. // Task stores the state for a scheduled task
  1084. type Task struct {
  1085. Name string `json:"name"`
  1086. UpdateAt int64 `json:"updated_at"`
  1087. Version int64 `json:"version"`
  1088. }