eventrule.go 60 KB

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