eventrule.go 60 KB

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