eventrule.go 53 KB

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