eventrule.go 49 KB

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