eventrule.go 45 KB

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