eventrule.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  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. )
  40. var (
  41. supportedEventActions = []int{ActionTypeHTTP, ActionTypeCommand, ActionTypeEmail, ActionTypeBackup,
  42. ActionTypeUserQuotaReset, ActionTypeFolderQuotaReset, ActionTypeTransferQuotaReset,
  43. ActionTypeDataRetentionCheck}
  44. )
  45. func isActionTypeValid(action int) bool {
  46. return util.Contains(supportedEventActions, action)
  47. }
  48. func getActionTypeAsString(action int) string {
  49. switch action {
  50. case ActionTypeHTTP:
  51. return "HTTP"
  52. case ActionTypeEmail:
  53. return "Email"
  54. case ActionTypeBackup:
  55. return "Backup"
  56. case ActionTypeUserQuotaReset:
  57. return "User quota reset"
  58. case ActionTypeFolderQuotaReset:
  59. return "Folder quota reset"
  60. case ActionTypeTransferQuotaReset:
  61. return "Transfer quota reset"
  62. case ActionTypeDataRetentionCheck:
  63. return "Data retention check"
  64. default:
  65. return "Command"
  66. }
  67. }
  68. // Supported event triggers
  69. const (
  70. // Filesystem events such as upload, download, mkdir ...
  71. EventTriggerFsEvent = iota + 1
  72. // Provider events such as add, update, delete
  73. EventTriggerProviderEvent
  74. EventTriggerSchedule
  75. )
  76. var (
  77. supportedEventTriggers = []int{EventTriggerFsEvent, EventTriggerProviderEvent, EventTriggerSchedule}
  78. )
  79. func isEventTriggerValid(trigger int) bool {
  80. return util.Contains(supportedEventTriggers, trigger)
  81. }
  82. func getTriggerTypeAsString(trigger int) string {
  83. switch trigger {
  84. case EventTriggerFsEvent:
  85. return "Filesystem event"
  86. case EventTriggerProviderEvent:
  87. return "Provider event"
  88. default:
  89. return "Schedule"
  90. }
  91. }
  92. // TODO: replace the copied strings with shared constants
  93. var (
  94. // SupportedFsEvents defines the supported filesystem events
  95. SupportedFsEvents = []string{"upload", "download", "delete", "rename", "mkdir", "rmdir", "ssh_cmd"}
  96. // SupportedProviderEvents defines the supported provider events
  97. SupportedProviderEvents = []string{operationAdd, operationUpdate, operationDelete}
  98. // SupportedRuleConditionProtocols defines the supported protcols for rule conditions
  99. SupportedRuleConditionProtocols = []string{"SFTP", "SCP", "SSH", "FTP", "DAV", "HTTP", "HTTPShare",
  100. "OIDC"}
  101. // SupporteRuleConditionProviderObjects defines the supported provider objects for rule conditions
  102. SupporteRuleConditionProviderObjects = []string{actionObjectUser, actionObjectGroup, actionObjectAdmin,
  103. actionObjectAPIKey, actionObjectShare, actionObjectEventRule, actionObjectEventAction}
  104. // SupportedHTTPActionMethods defines the supported methods for HTTP actions
  105. SupportedHTTPActionMethods = []string{http.MethodPost, http.MethodGet, http.MethodPut}
  106. )
  107. // enum mappings
  108. var (
  109. EventActionTypes []EnumMapping
  110. EventTriggerTypes []EnumMapping
  111. )
  112. func init() {
  113. for _, t := range supportedEventActions {
  114. EventActionTypes = append(EventActionTypes, EnumMapping{
  115. Value: t,
  116. Name: getActionTypeAsString(t),
  117. })
  118. }
  119. for _, t := range supportedEventTriggers {
  120. EventTriggerTypes = append(EventTriggerTypes, EnumMapping{
  121. Value: t,
  122. Name: getTriggerTypeAsString(t),
  123. })
  124. }
  125. }
  126. // EnumMapping defines a mapping between enum values and names
  127. type EnumMapping struct {
  128. Name string
  129. Value int
  130. }
  131. // KeyValue defines a key/value pair
  132. type KeyValue struct {
  133. Key string `json:"key"`
  134. Value string `json:"value"`
  135. }
  136. // EventActionHTTPConfig defines the configuration for an HTTP event target
  137. type EventActionHTTPConfig struct {
  138. Endpoint string `json:"endpoint,omitempty"`
  139. Username string `json:"username,omitempty"`
  140. Password *kms.Secret `json:"password,omitempty"`
  141. Headers []KeyValue `json:"headers,omitempty"`
  142. Timeout int `json:"timeout,omitempty"`
  143. SkipTLSVerify bool `json:"skip_tls_verify,omitempty"`
  144. Method string `json:"method,omitempty"`
  145. QueryParameters []KeyValue `json:"query_parameters,omitempty"`
  146. Body string `json:"post_body,omitempty"`
  147. }
  148. func (c *EventActionHTTPConfig) validate(additionalData string) error {
  149. if c.Endpoint == "" {
  150. return util.NewValidationError("HTTP endpoint is required")
  151. }
  152. if !util.IsStringPrefixInSlice(c.Endpoint, []string{"http://", "https://"}) {
  153. return util.NewValidationError("invalid HTTP endpoint schema: http and https are supported")
  154. }
  155. if c.Timeout < 1 || c.Timeout > 120 {
  156. return util.NewValidationError(fmt.Sprintf("invalid HTTP timeout %d", c.Timeout))
  157. }
  158. for _, kv := range c.Headers {
  159. if kv.Key == "" || kv.Value == "" {
  160. return util.NewValidationError("invalid HTTP headers")
  161. }
  162. }
  163. if c.Password.IsRedacted() {
  164. return util.NewValidationError("cannot save HTTP configuration with a redacted secret")
  165. }
  166. if c.Password.IsPlain() {
  167. c.Password.SetAdditionalData(additionalData)
  168. err := c.Password.Encrypt()
  169. if err != nil {
  170. return util.NewValidationError(fmt.Sprintf("could not encrypt HTTP password: %v", err))
  171. }
  172. }
  173. if !util.Contains(SupportedHTTPActionMethods, c.Method) {
  174. return util.NewValidationError(fmt.Sprintf("unsupported HTTP method: %s", c.Method))
  175. }
  176. for _, kv := range c.QueryParameters {
  177. if kv.Key == "" || kv.Value == "" {
  178. return util.NewValidationError("invalid HTTP query parameters")
  179. }
  180. }
  181. return nil
  182. }
  183. // GetHTTPClient returns an HTTP client based on the config
  184. func (c *EventActionHTTPConfig) GetHTTPClient() *http.Client {
  185. client := &http.Client{
  186. Timeout: time.Duration(c.Timeout) * time.Second,
  187. }
  188. if c.SkipTLSVerify {
  189. transport := http.DefaultTransport.(*http.Transport).Clone()
  190. if transport.TLSClientConfig != nil {
  191. transport.TLSClientConfig.InsecureSkipVerify = true
  192. } else {
  193. transport.TLSClientConfig = &tls.Config{
  194. NextProtos: []string{"http/1.1", "h2"},
  195. InsecureSkipVerify: true,
  196. }
  197. }
  198. client.Transport = transport
  199. }
  200. return client
  201. }
  202. // EventActionCommandConfig defines the configuration for a command event target
  203. type EventActionCommandConfig struct {
  204. Cmd string `json:"cmd,omitempty"`
  205. Timeout int `json:"timeout,omitempty"`
  206. EnvVars []KeyValue `json:"env_vars,omitempty"`
  207. }
  208. func (c *EventActionCommandConfig) validate() error {
  209. if c.Cmd == "" {
  210. return util.NewValidationError("command is required")
  211. }
  212. if !filepath.IsAbs(c.Cmd) {
  213. return util.NewValidationError("invalid command, it must be an absolute path")
  214. }
  215. if c.Timeout < 1 || c.Timeout > 120 {
  216. return util.NewValidationError(fmt.Sprintf("invalid command action timeout %d", c.Timeout))
  217. }
  218. for _, kv := range c.EnvVars {
  219. if kv.Key == "" || kv.Value == "" {
  220. return util.NewValidationError("invalid command env vars")
  221. }
  222. }
  223. return nil
  224. }
  225. // EventActionEmailConfig defines the configuration options for SMTP event actions
  226. type EventActionEmailConfig struct {
  227. Recipients []string `json:"recipients,omitempty"`
  228. Subject string `json:"subject,omitempty"`
  229. Body string `json:"body,omitempty"`
  230. }
  231. // GetRecipientsAsString returns the list of recipients as comma separated string
  232. func (c EventActionEmailConfig) GetRecipientsAsString() string {
  233. return strings.Join(c.Recipients, ",")
  234. }
  235. func (c *EventActionEmailConfig) validate() error {
  236. if len(c.Recipients) == 0 {
  237. return util.NewValidationError("at least one email recipient is required")
  238. }
  239. c.Recipients = util.RemoveDuplicates(c.Recipients, false)
  240. for _, r := range c.Recipients {
  241. if r == "" {
  242. return util.NewValidationError("invalid email recipients")
  243. }
  244. }
  245. if c.Subject == "" {
  246. return util.NewValidationError("email subject is required")
  247. }
  248. if c.Body == "" {
  249. return util.NewValidationError("email body is required")
  250. }
  251. return nil
  252. }
  253. // FolderRetention defines a folder retention configuration
  254. type FolderRetention struct {
  255. // Path is the exposed virtual directory path, if no other specific retention is defined,
  256. // the retention applies for sub directories too. For example if retention is defined
  257. // for the paths "/" and "/sub" then the retention for "/" is applied for any file outside
  258. // the "/sub" directory
  259. Path string `json:"path"`
  260. // Retention time in hours. 0 means exclude this path
  261. Retention int `json:"retention"`
  262. // DeleteEmptyDirs defines if empty directories will be deleted.
  263. // The user need the delete permission
  264. DeleteEmptyDirs bool `json:"delete_empty_dirs,omitempty"`
  265. // IgnoreUserPermissions defines whether to delete files even if the user does not have the delete permission.
  266. // The default is "false" which means that files will be skipped if the user does not have the permission
  267. // to delete them. This applies to sub directories too.
  268. IgnoreUserPermissions bool `json:"ignore_user_permissions,omitempty"`
  269. }
  270. // Validate returns an error if the configuration is not valid
  271. func (f *FolderRetention) Validate() error {
  272. f.Path = util.CleanPath(f.Path)
  273. if f.Retention < 0 {
  274. return util.NewValidationError(fmt.Sprintf("invalid folder retention %v, it must be greater or equal to zero",
  275. f.Retention))
  276. }
  277. return nil
  278. }
  279. // EventActionDataRetentionConfig defines the configuration for a data retention check
  280. type EventActionDataRetentionConfig struct {
  281. Folders []FolderRetention `json:"folders,omitempty"`
  282. }
  283. func (c *EventActionDataRetentionConfig) validate() error {
  284. folderPaths := make(map[string]bool)
  285. nothingToDo := true
  286. for idx := range c.Folders {
  287. f := &c.Folders[idx]
  288. if err := f.Validate(); err != nil {
  289. return err
  290. }
  291. if f.Retention > 0 {
  292. nothingToDo = false
  293. }
  294. if _, ok := folderPaths[f.Path]; ok {
  295. return util.NewValidationError(fmt.Sprintf("duplicated folder path %#v", f.Path))
  296. }
  297. folderPaths[f.Path] = true
  298. }
  299. if nothingToDo {
  300. return util.NewValidationError("nothing to delete!")
  301. }
  302. return nil
  303. }
  304. // BaseEventActionOptions defines the supported configuration options for a base event actions
  305. type BaseEventActionOptions struct {
  306. HTTPConfig EventActionHTTPConfig `json:"http_config"`
  307. CmdConfig EventActionCommandConfig `json:"cmd_config"`
  308. EmailConfig EventActionEmailConfig `json:"email_config"`
  309. RetentionConfig EventActionDataRetentionConfig `json:"retention_config"`
  310. }
  311. func (o *BaseEventActionOptions) getACopy() BaseEventActionOptions {
  312. o.SetEmptySecretsIfNil()
  313. emailRecipients := make([]string, len(o.EmailConfig.Recipients))
  314. copy(emailRecipients, o.EmailConfig.Recipients)
  315. folders := make([]FolderRetention, 0, len(o.RetentionConfig.Folders))
  316. for _, folder := range o.RetentionConfig.Folders {
  317. folders = append(folders, FolderRetention{
  318. Path: folder.Path,
  319. Retention: folder.Retention,
  320. DeleteEmptyDirs: folder.DeleteEmptyDirs,
  321. IgnoreUserPermissions: folder.IgnoreUserPermissions,
  322. })
  323. }
  324. return BaseEventActionOptions{
  325. HTTPConfig: EventActionHTTPConfig{
  326. Endpoint: o.HTTPConfig.Endpoint,
  327. Username: o.HTTPConfig.Username,
  328. Password: o.HTTPConfig.Password.Clone(),
  329. Headers: cloneKeyValues(o.HTTPConfig.Headers),
  330. Timeout: o.HTTPConfig.Timeout,
  331. SkipTLSVerify: o.HTTPConfig.SkipTLSVerify,
  332. Method: o.HTTPConfig.Method,
  333. QueryParameters: cloneKeyValues(o.HTTPConfig.QueryParameters),
  334. Body: o.HTTPConfig.Body,
  335. },
  336. CmdConfig: EventActionCommandConfig{
  337. Cmd: o.CmdConfig.Cmd,
  338. Timeout: o.CmdConfig.Timeout,
  339. EnvVars: cloneKeyValues(o.CmdConfig.EnvVars),
  340. },
  341. EmailConfig: EventActionEmailConfig{
  342. Recipients: emailRecipients,
  343. Subject: o.EmailConfig.Subject,
  344. Body: o.EmailConfig.Body,
  345. },
  346. RetentionConfig: EventActionDataRetentionConfig{
  347. Folders: folders,
  348. },
  349. }
  350. }
  351. // SetEmptySecretsIfNil sets the secrets to empty if nil
  352. func (o *BaseEventActionOptions) SetEmptySecretsIfNil() {
  353. if o.HTTPConfig.Password == nil {
  354. o.HTTPConfig.Password = kms.NewEmptySecret()
  355. }
  356. }
  357. func (o *BaseEventActionOptions) setNilSecretsIfEmpty() {
  358. if o.HTTPConfig.Password != nil && o.HTTPConfig.Password.IsEmpty() {
  359. o.HTTPConfig.Password = nil
  360. }
  361. }
  362. func (o *BaseEventActionOptions) hideConfidentialData() {
  363. if o.HTTPConfig.Password != nil {
  364. o.HTTPConfig.Password.Hide()
  365. }
  366. }
  367. func (o *BaseEventActionOptions) validate(action int, name string) error {
  368. o.SetEmptySecretsIfNil()
  369. switch action {
  370. case ActionTypeHTTP:
  371. o.CmdConfig = EventActionCommandConfig{}
  372. o.EmailConfig = EventActionEmailConfig{}
  373. o.RetentionConfig = EventActionDataRetentionConfig{}
  374. return o.HTTPConfig.validate(name)
  375. case ActionTypeCommand:
  376. o.HTTPConfig = EventActionHTTPConfig{}
  377. o.EmailConfig = EventActionEmailConfig{}
  378. o.RetentionConfig = EventActionDataRetentionConfig{}
  379. return o.CmdConfig.validate()
  380. case ActionTypeEmail:
  381. o.HTTPConfig = EventActionHTTPConfig{}
  382. o.CmdConfig = EventActionCommandConfig{}
  383. o.RetentionConfig = EventActionDataRetentionConfig{}
  384. return o.EmailConfig.validate()
  385. case ActionTypeDataRetentionCheck:
  386. o.HTTPConfig = EventActionHTTPConfig{}
  387. o.CmdConfig = EventActionCommandConfig{}
  388. o.EmailConfig = EventActionEmailConfig{}
  389. return o.RetentionConfig.validate()
  390. default:
  391. o.HTTPConfig = EventActionHTTPConfig{}
  392. o.CmdConfig = EventActionCommandConfig{}
  393. o.EmailConfig = EventActionEmailConfig{}
  394. o.RetentionConfig = EventActionDataRetentionConfig{}
  395. }
  396. return nil
  397. }
  398. // BaseEventAction defines the common fields for an event action
  399. type BaseEventAction struct {
  400. // Data provider unique identifier
  401. ID int64 `json:"id"`
  402. // Action name
  403. Name string `json:"name"`
  404. // optional description
  405. Description string `json:"description,omitempty"`
  406. // ActionType, see the above enum
  407. Type int `json:"type"`
  408. // Configuration options specific for the action type
  409. Options BaseEventActionOptions `json:"options"`
  410. // list of rule names associated with this event action
  411. Rules []string `json:"rules,omitempty"`
  412. }
  413. func (a *BaseEventAction) getACopy() BaseEventAction {
  414. rules := make([]string, len(a.Rules))
  415. copy(rules, a.Rules)
  416. return BaseEventAction{
  417. ID: a.ID,
  418. Name: a.Name,
  419. Description: a.Description,
  420. Type: a.Type,
  421. Options: a.Options.getACopy(),
  422. Rules: rules,
  423. }
  424. }
  425. // GetTypeAsString returns the action type as string
  426. func (a *BaseEventAction) GetTypeAsString() string {
  427. return getActionTypeAsString(a.Type)
  428. }
  429. // GetRulesAsString returns the list of rules as comma separated string
  430. func (a *BaseEventAction) GetRulesAsString() string {
  431. return strings.Join(a.Rules, ",")
  432. }
  433. // PrepareForRendering prepares a BaseEventAction for rendering.
  434. // It hides confidential data and set to nil the empty secrets
  435. // so they are not serialized
  436. func (a *BaseEventAction) PrepareForRendering() {
  437. a.Options.setNilSecretsIfEmpty()
  438. a.Options.hideConfidentialData()
  439. }
  440. // RenderAsJSON implements the renderer interface used within plugins
  441. func (a *BaseEventAction) RenderAsJSON(reload bool) ([]byte, error) {
  442. if reload {
  443. action, err := provider.eventActionExists(a.Name)
  444. if err != nil {
  445. providerLog(logger.LevelError, "unable to reload event action before rendering as json: %v", err)
  446. return nil, err
  447. }
  448. action.PrepareForRendering()
  449. return json.Marshal(action)
  450. }
  451. a.PrepareForRendering()
  452. return json.Marshal(a)
  453. }
  454. func (a *BaseEventAction) validate() error {
  455. if a.Name == "" {
  456. return util.NewValidationError("name is mandatory")
  457. }
  458. if !isActionTypeValid(a.Type) {
  459. return util.NewValidationError(fmt.Sprintf("invalid action type: %d", a.Type))
  460. }
  461. return a.Options.validate(a.Type, a.Name)
  462. }
  463. // EventActionOptions defines the supported configuration options for an event action
  464. type EventActionOptions struct {
  465. IsFailureAction bool `json:"is_failure_action"`
  466. StopOnFailure bool `json:"stop_on_failure"`
  467. ExecuteSync bool `json:"execute_sync"`
  468. }
  469. // EventAction defines an event action
  470. type EventAction struct {
  471. BaseEventAction
  472. // Order defines the execution order
  473. Order int `json:"order,omitempty"`
  474. Options EventActionOptions `json:"relation_options"`
  475. }
  476. func (a *EventAction) getACopy() EventAction {
  477. return EventAction{
  478. BaseEventAction: a.BaseEventAction.getACopy(),
  479. Order: a.Order,
  480. Options: EventActionOptions{
  481. IsFailureAction: a.Options.IsFailureAction,
  482. StopOnFailure: a.Options.StopOnFailure,
  483. ExecuteSync: a.Options.ExecuteSync,
  484. },
  485. }
  486. }
  487. func (a *EventAction) validateAssociation(trigger int, fsEvents []string) error {
  488. if a.Options.IsFailureAction {
  489. if a.Options.ExecuteSync {
  490. return util.NewValidationError("sync execution is not supported for failure actions")
  491. }
  492. }
  493. if trigger != EventTriggerFsEvent || !util.Contains(fsEvents, "upload") {
  494. if a.Options.ExecuteSync {
  495. return util.NewValidationError("sync execution is only supported for upload event")
  496. }
  497. }
  498. return nil
  499. }
  500. // ConditionPattern defines a pattern for condition filters
  501. type ConditionPattern struct {
  502. Pattern string `json:"pattern,omitempty"`
  503. InverseMatch bool `json:"inverse_match,omitempty"`
  504. }
  505. func (p *ConditionPattern) validate() error {
  506. if p.Pattern == "" {
  507. return util.NewValidationError("empty condition pattern not allowed")
  508. }
  509. _, err := path.Match(p.Pattern, "abc")
  510. if err != nil {
  511. return util.NewValidationError(fmt.Sprintf("invalid condition pattern %q", p.Pattern))
  512. }
  513. return nil
  514. }
  515. // ConditionOptions defines options for event conditions
  516. type ConditionOptions struct {
  517. // Usernames or folder names
  518. Names []ConditionPattern `json:"names,omitempty"`
  519. // Virtual paths
  520. FsPaths []ConditionPattern `json:"fs_paths,omitempty"`
  521. Protocols []string `json:"protocols,omitempty"`
  522. ProviderObjects []string `json:"provider_objects,omitempty"`
  523. MinFileSize int64 `json:"min_size,omitempty"`
  524. MaxFileSize int64 `json:"max_size,omitempty"`
  525. // allow to execute scheduled tasks concurrently from multiple instances
  526. ConcurrentExecution bool `json:"concurrent_execution,omitempty"`
  527. }
  528. func (f *ConditionOptions) getACopy() ConditionOptions {
  529. protocols := make([]string, len(f.Protocols))
  530. copy(protocols, f.Protocols)
  531. providerObjects := make([]string, len(f.ProviderObjects))
  532. copy(providerObjects, f.ProviderObjects)
  533. return ConditionOptions{
  534. Names: cloneConditionPatterns(f.Names),
  535. FsPaths: cloneConditionPatterns(f.FsPaths),
  536. Protocols: protocols,
  537. ProviderObjects: providerObjects,
  538. MinFileSize: f.MinFileSize,
  539. MaxFileSize: f.MaxFileSize,
  540. ConcurrentExecution: f.ConcurrentExecution,
  541. }
  542. }
  543. func (f *ConditionOptions) validate() error {
  544. for _, name := range f.Names {
  545. if err := name.validate(); err != nil {
  546. return err
  547. }
  548. }
  549. for _, fsPath := range f.FsPaths {
  550. if err := fsPath.validate(); err != nil {
  551. return err
  552. }
  553. }
  554. for _, p := range f.Protocols {
  555. if !util.Contains(SupportedRuleConditionProtocols, p) {
  556. return util.NewValidationError(fmt.Sprintf("unsupported rule condition protocol: %q", p))
  557. }
  558. }
  559. for _, p := range f.ProviderObjects {
  560. if !util.Contains(SupporteRuleConditionProviderObjects, p) {
  561. return util.NewValidationError(fmt.Sprintf("unsupported provider object: %q", p))
  562. }
  563. }
  564. if f.MinFileSize > 0 && f.MaxFileSize > 0 {
  565. if f.MaxFileSize <= f.MinFileSize {
  566. return util.NewValidationError(fmt.Sprintf("invalid max file size %d, it is lesser or equal than min file size %d",
  567. f.MaxFileSize, f.MinFileSize))
  568. }
  569. }
  570. if config.IsShared == 0 {
  571. f.ConcurrentExecution = false
  572. }
  573. return nil
  574. }
  575. // Schedule defines an event schedule
  576. type Schedule struct {
  577. Hours string `json:"hour"`
  578. DayOfWeek string `json:"day_of_week"`
  579. DayOfMonth string `json:"day_of_month"`
  580. Month string `json:"month"`
  581. }
  582. // GetCronSpec returns the cron compatible schedule string
  583. func (s *Schedule) GetCronSpec() string {
  584. return fmt.Sprintf("0 %s %s %s %s", s.Hours, s.DayOfMonth, s.Month, s.DayOfWeek)
  585. }
  586. func (s *Schedule) validate() error {
  587. _, err := cron.ParseStandard(s.GetCronSpec())
  588. if err != nil {
  589. return util.NewValidationError(fmt.Sprintf("invalid schedule, hour: %q, day of month: %q, month: %q, day of week: %q",
  590. s.Hours, s.DayOfMonth, s.Month, s.DayOfWeek))
  591. }
  592. return nil
  593. }
  594. // EventConditions defines the conditions for an event rule
  595. type EventConditions struct {
  596. // Only one between FsEvents, ProviderEvents and Schedule is allowed
  597. FsEvents []string `json:"fs_events,omitempty"`
  598. ProviderEvents []string `json:"provider_events,omitempty"`
  599. Schedules []Schedule `json:"schedules,omitempty"`
  600. Options ConditionOptions `json:"options"`
  601. }
  602. func (c *EventConditions) getACopy() EventConditions {
  603. fsEvents := make([]string, len(c.FsEvents))
  604. copy(fsEvents, c.FsEvents)
  605. providerEvents := make([]string, len(c.ProviderEvents))
  606. copy(providerEvents, c.ProviderEvents)
  607. schedules := make([]Schedule, 0, len(c.Schedules))
  608. for _, schedule := range c.Schedules {
  609. schedules = append(schedules, Schedule{
  610. Hours: schedule.Hours,
  611. DayOfWeek: schedule.DayOfWeek,
  612. DayOfMonth: schedule.DayOfMonth,
  613. Month: schedule.Month,
  614. })
  615. }
  616. return EventConditions{
  617. FsEvents: fsEvents,
  618. ProviderEvents: providerEvents,
  619. Schedules: schedules,
  620. Options: c.Options.getACopy(),
  621. }
  622. }
  623. func (c *EventConditions) validate(trigger int) error {
  624. switch trigger {
  625. case EventTriggerFsEvent:
  626. c.ProviderEvents = nil
  627. c.Schedules = nil
  628. c.Options.ProviderObjects = nil
  629. if len(c.FsEvents) == 0 {
  630. return util.NewValidationError("at least one filesystem event is required")
  631. }
  632. for _, ev := range c.FsEvents {
  633. if !util.Contains(SupportedFsEvents, ev) {
  634. return util.NewValidationError(fmt.Sprintf("unsupported fs event: %q", ev))
  635. }
  636. }
  637. case EventTriggerProviderEvent:
  638. c.FsEvents = nil
  639. c.Schedules = nil
  640. c.Options.FsPaths = nil
  641. c.Options.Protocols = nil
  642. c.Options.MinFileSize = 0
  643. c.Options.MaxFileSize = 0
  644. if len(c.ProviderEvents) == 0 {
  645. return util.NewValidationError("at least one provider event is required")
  646. }
  647. for _, ev := range c.ProviderEvents {
  648. if !util.Contains(SupportedProviderEvents, ev) {
  649. return util.NewValidationError(fmt.Sprintf("unsupported provider event: %q", ev))
  650. }
  651. }
  652. case EventTriggerSchedule:
  653. c.FsEvents = nil
  654. c.ProviderEvents = nil
  655. c.Options.FsPaths = nil
  656. c.Options.Protocols = nil
  657. c.Options.MinFileSize = 0
  658. c.Options.MaxFileSize = 0
  659. c.Options.ProviderObjects = nil
  660. if len(c.Schedules) == 0 {
  661. return util.NewValidationError("at least one schedule is required")
  662. }
  663. for _, schedule := range c.Schedules {
  664. if err := schedule.validate(); err != nil {
  665. return err
  666. }
  667. }
  668. default:
  669. c.FsEvents = nil
  670. c.ProviderEvents = nil
  671. c.Options.FsPaths = nil
  672. c.Options.Protocols = nil
  673. c.Options.MinFileSize = 0
  674. c.Options.MaxFileSize = 0
  675. c.Schedules = nil
  676. }
  677. return c.Options.validate()
  678. }
  679. // EventRule defines the trigger, conditions and actions for an event
  680. type EventRule struct {
  681. // Data provider unique identifier
  682. ID int64 `json:"id"`
  683. // Rule name
  684. Name string `json:"name"`
  685. // optional description
  686. Description string `json:"description,omitempty"`
  687. // Creation time as unix timestamp in milliseconds
  688. CreatedAt int64 `json:"created_at"`
  689. // last update time as unix timestamp in milliseconds
  690. UpdatedAt int64 `json:"updated_at"`
  691. // Event trigger
  692. Trigger int `json:"trigger"`
  693. // Event conditions
  694. Conditions EventConditions `json:"conditions"`
  695. // actions to execute
  696. Actions []EventAction `json:"actions"`
  697. // in multi node setups we mark the rule as deleted to be able to update the cache
  698. DeletedAt int64 `json:"-"`
  699. }
  700. func (r *EventRule) getACopy() EventRule {
  701. actions := make([]EventAction, 0, len(r.Actions))
  702. for _, action := range r.Actions {
  703. actions = append(actions, action.getACopy())
  704. }
  705. return EventRule{
  706. ID: r.ID,
  707. Name: r.Name,
  708. Description: r.Description,
  709. CreatedAt: r.CreatedAt,
  710. UpdatedAt: r.UpdatedAt,
  711. Trigger: r.Trigger,
  712. Conditions: r.Conditions.getACopy(),
  713. Actions: actions,
  714. DeletedAt: r.DeletedAt,
  715. }
  716. }
  717. // GuardFromConcurrentExecution returns true if the rule cannot be executed concurrently
  718. // from multiple instances
  719. func (r *EventRule) GuardFromConcurrentExecution() bool {
  720. if config.IsShared == 0 {
  721. return false
  722. }
  723. return !r.Conditions.Options.ConcurrentExecution
  724. }
  725. // GetTriggerAsString returns the rule trigger as string
  726. func (r *EventRule) GetTriggerAsString() string {
  727. return getTriggerTypeAsString(r.Trigger)
  728. }
  729. // GetActionsAsString returns the list of action names as comma separated string
  730. func (r *EventRule) GetActionsAsString() string {
  731. actions := make([]string, 0, len(r.Actions))
  732. for _, action := range r.Actions {
  733. actions = append(actions, action.Name)
  734. }
  735. return strings.Join(actions, ",")
  736. }
  737. func (r *EventRule) validate() error {
  738. if r.Name == "" {
  739. return util.NewValidationError("name is mandatory")
  740. }
  741. if !isEventTriggerValid(r.Trigger) {
  742. return util.NewValidationError(fmt.Sprintf("invalid event rule trigger: %d", r.Trigger))
  743. }
  744. if err := r.Conditions.validate(r.Trigger); err != nil {
  745. return err
  746. }
  747. if len(r.Actions) == 0 {
  748. return util.NewValidationError("at least one action is required")
  749. }
  750. actionNames := make(map[string]bool)
  751. actionOrders := make(map[int]bool)
  752. failureActions := 0
  753. for idx := range r.Actions {
  754. if r.Actions[idx].Name == "" {
  755. return util.NewValidationError(fmt.Sprintf("invalid action at position %d, name not specified", idx))
  756. }
  757. if actionNames[r.Actions[idx].Name] {
  758. return util.NewValidationError(fmt.Sprintf("duplicated action %q", r.Actions[idx].Name))
  759. }
  760. if actionOrders[r.Actions[idx].Order] {
  761. return util.NewValidationError(fmt.Sprintf("duplicated order %d for action %q",
  762. r.Actions[idx].Order, r.Actions[idx].Name))
  763. }
  764. if err := r.Actions[idx].validateAssociation(r.Trigger, r.Conditions.FsEvents); err != nil {
  765. return err
  766. }
  767. if r.Actions[idx].Options.IsFailureAction {
  768. failureActions++
  769. }
  770. actionNames[r.Actions[idx].Name] = true
  771. actionOrders[r.Actions[idx].Order] = true
  772. }
  773. if len(r.Actions) == failureActions {
  774. return util.NewValidationError("at least a non-failure action is required")
  775. }
  776. return nil
  777. }
  778. // PrepareForRendering prepares an EventRule for rendering.
  779. // It hides confidential data and set to nil the empty secrets
  780. // so they are not serialized
  781. func (r *EventRule) PrepareForRendering() {
  782. for idx := range r.Actions {
  783. r.Actions[idx].PrepareForRendering()
  784. }
  785. }
  786. // RenderAsJSON implements the renderer interface used within plugins
  787. func (r *EventRule) RenderAsJSON(reload bool) ([]byte, error) {
  788. if reload {
  789. rule, err := provider.eventRuleExists(r.Name)
  790. if err != nil {
  791. providerLog(logger.LevelError, "unable to reload event rule before rendering as json: %v", err)
  792. return nil, err
  793. }
  794. rule.PrepareForRendering()
  795. return json.Marshal(rule)
  796. }
  797. r.PrepareForRendering()
  798. return json.Marshal(r)
  799. }
  800. func cloneKeyValues(keyVals []KeyValue) []KeyValue {
  801. res := make([]KeyValue, 0, len(keyVals))
  802. for _, kv := range keyVals {
  803. res = append(res, KeyValue{
  804. Key: kv.Key,
  805. Value: kv.Value,
  806. })
  807. }
  808. return res
  809. }
  810. func cloneConditionPatterns(patterns []ConditionPattern) []ConditionPattern {
  811. res := make([]ConditionPattern, 0, len(patterns))
  812. for _, p := range patterns {
  813. res = append(res, ConditionPattern{
  814. Pattern: p.Pattern,
  815. InverseMatch: p.InverseMatch,
  816. })
  817. }
  818. return res
  819. }
  820. // Task stores the state for a scheduled task
  821. type Task struct {
  822. Name string `json:"name"`
  823. UpdateAt int64 `json:"updated_at"`
  824. Version int64 `json:"version"`
  825. }