eventrule.go 49 KB

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