diff --git a/internal/common/eventmanager.go b/internal/common/eventmanager.go index 9368ddd3..d35d48f1 100644 --- a/internal/common/eventmanager.go +++ b/internal/common/eventmanager.go @@ -262,6 +262,9 @@ func (r *eventRulesContainer) checkProviderEventMatch(conditions dataprovider.Ev if !checkEventConditionPatterns(params.Name, conditions.Options.Names) { return false } + if !checkEventConditionPatterns(params.Role, conditions.Options.RoleNames) { + return false + } if len(conditions.Options.ProviderObjects) > 0 && !util.Contains(conditions.Options.ProviderObjects, params.ObjectType) { return false } @@ -275,6 +278,9 @@ func (r *eventRulesContainer) checkFsEventMatch(conditions dataprovider.EventCon if !checkEventConditionPatterns(params.Name, conditions.Options.Names) { return false } + if !checkEventConditionPatterns(params.Role, conditions.Options.RoleNames) { + return false + } if !checkEventGroupConditionPatters(params.Groups, conditions.Options.GroupNames) { return false } @@ -919,6 +925,19 @@ func checkEventConditionPattern(p dataprovider.ConditionPattern, name string) bo return matched } +func checkUserConditionOptions(user *dataprovider.User, conditions *dataprovider.ConditionOptions) bool { + if !checkEventConditionPatterns(user.Username, conditions.Names) { + return false + } + if !checkEventConditionPatterns(user.Role, conditions.RoleNames) { + return false + } + if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { + return false + } + return true +} + // checkConditionPatterns returns false if patterns are defined and no match is found func checkEventConditionPatterns(name string, patterns []dataprovider.ConditionPattern) bool { if len(patterns) == 0 { @@ -1302,13 +1321,8 @@ func executeDeleteFsRuleAction(deletes []string, replacer *strings.Replacer, for _, user := range users { // if sender is set, the conditions have already been evaluated if params.sender == "" { - if !checkEventConditionPatterns(user.Username, conditions.Names) { - eventManagerLog(logger.LevelDebug, "skipping fs delete for user %s, name conditions don't match", - user.Username) - continue - } - if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { - eventManagerLog(logger.LevelDebug, "skipping fs delete for user %s, group name conditions don't match", + if !checkUserConditionOptions(&user, &conditions) { + eventManagerLog(logger.LevelDebug, "skipping fs delete for user %s, condition options don't match", user.Username) continue } @@ -1366,13 +1380,8 @@ func executeMkdirFsRuleAction(dirs []string, replacer *strings.Replacer, for _, user := range users { // if sender is set, the conditions have already been evaluated if params.sender == "" { - if !checkEventConditionPatterns(user.Username, conditions.Names) { - eventManagerLog(logger.LevelDebug, "skipping fs mkdir for user %s, name conditions don't match", - user.Username) - continue - } - if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { - eventManagerLog(logger.LevelDebug, "skipping fs mkdir for user %s, group name conditions don't match", + if !checkUserConditionOptions(&user, &conditions) { + eventManagerLog(logger.LevelDebug, "skipping fs mkdir for user %s, condition options don't match", user.Username) continue } @@ -1453,13 +1462,8 @@ func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *string for _, user := range users { // if sender is set, the conditions have already been evaluated if params.sender == "" { - if !checkEventConditionPatterns(user.Username, conditions.Names) { - eventManagerLog(logger.LevelDebug, "skipping fs rename for user %s, name conditions don't match", - user.Username) - continue - } - if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { - eventManagerLog(logger.LevelDebug, "skipping fs rename for user %s, group name conditions don't match", + if !checkUserConditionOptions(&user, &conditions) { + eventManagerLog(logger.LevelDebug, "skipping fs rename for user %s, condition options don't match", user.Username) continue } @@ -1559,13 +1563,8 @@ func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, condit for _, user := range users { // if sender is set, the conditions have already been evaluated if params.sender == "" { - if !checkEventConditionPatterns(user.Username, conditions.Names) { - eventManagerLog(logger.LevelDebug, "skipping fs exist for user %s, name conditions don't match", - user.Username) - continue - } - if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { - eventManagerLog(logger.LevelDebug, "skipping fs exist for user %s, group name conditions don't match", + if !checkUserConditionOptions(&user, &conditions) { + eventManagerLog(logger.LevelDebug, "skipping fs exist for user %s, condition options don't match", user.Username) continue } @@ -1599,13 +1598,8 @@ func executeCompressFsRuleAction(c dataprovider.EventActionFsCompress, replacer for _, user := range users { // if sender is set, the conditions have already been evaluated if params.sender == "" { - if !checkEventConditionPatterns(user.Username, conditions.Names) { - eventManagerLog(logger.LevelDebug, "skipping fs compress for user %s, name conditions don't match", - user.Username) - continue - } - if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { - eventManagerLog(logger.LevelDebug, "skipping fs compress for user %s, group name conditions don't match", + if !checkUserConditionOptions(&user, &conditions) { + eventManagerLog(logger.LevelDebug, "skipping fs compress for user %s, condition options don't match", user.Username) continue } @@ -1684,13 +1678,8 @@ func executeUsersQuotaResetRuleAction(conditions dataprovider.ConditionOptions, for _, user := range users { // if sender is set, the conditions have already been evaluated if params.sender == "" { - if !checkEventConditionPatterns(user.Username, conditions.Names) { - eventManagerLog(logger.LevelDebug, "skipping quota reset for user %q, name conditions don't match", - user.Username) - continue - } - if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { - eventManagerLog(logger.LevelDebug, "skipping quota reset for user %q, group name conditions don't match", + if !checkUserConditionOptions(&user, &conditions) { + eventManagerLog(logger.LevelDebug, "skipping quota reset for user %q, condition options don't match", user.Username) continue } @@ -1772,13 +1761,8 @@ func executeTransferQuotaResetRuleAction(conditions dataprovider.ConditionOption for _, user := range users { // if sender is set, the conditions have already been evaluated if params.sender == "" { - if !checkEventConditionPatterns(user.Username, conditions.Names) { - eventManagerLog(logger.LevelDebug, "skipping scheduled transfer quota reset for user %s, name conditions don't match", - user.Username) - continue - } - if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { - eventManagerLog(logger.LevelDebug, "skipping scheduled transfer quota reset for user %s, group name conditions don't match", + if !checkUserConditionOptions(&user, &conditions) { + eventManagerLog(logger.LevelDebug, "skipping scheduled transfer quota reset for user %s, condition options don't match", user.Username) continue } @@ -1843,13 +1827,8 @@ func executeDataRetentionCheckRuleAction(config dataprovider.EventActionDataRete for _, user := range users { // if sender is set, the conditions have already been evaluated if params.sender == "" { - if !checkEventConditionPatterns(user.Username, conditions.Names) { - eventManagerLog(logger.LevelDebug, "skipping scheduled retention check for user %s, name conditions don't match", - user.Username) - continue - } - if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { - eventManagerLog(logger.LevelDebug, "skipping scheduled retention check for user %s, group name conditions don't match", + if !checkUserConditionOptions(&user, &conditions) { + eventManagerLog(logger.LevelDebug, "skipping scheduled retention check for user %s, condition options don't match", user.Username) continue } @@ -1900,13 +1879,8 @@ func executeMetadataCheckRuleAction(conditions dataprovider.ConditionOptions, pa for _, user := range users { // if sender is set, the conditions have already been evaluated if params.sender == "" { - if !checkEventConditionPatterns(user.Username, conditions.Names) { - eventManagerLog(logger.LevelDebug, "skipping metadata check for user %q, name conditions don't match", - user.Username) - continue - } - if !checkEventGroupConditionPatters(user.Groups, conditions.GroupNames) { - eventManagerLog(logger.LevelDebug, "skipping metadata check for user %q, group name conditions don't match", + if !checkUserConditionOptions(&user, &conditions) { + eventManagerLog(logger.LevelDebug, "skipping metadata check for user %q, condition options don't match", user.Username) continue } diff --git a/internal/common/eventmanager_test.go b/internal/common/eventmanager_test.go index f67e6c6e..4c314a24 100644 --- a/internal/common/eventmanager_test.go +++ b/internal/common/eventmanager_test.go @@ -42,6 +42,7 @@ import ( ) func TestEventRuleMatch(t *testing.T) { + role := "role1" conditions := dataprovider.EventConditions{ ProviderEvents: []string{"add", "update"}, Options: dataprovider.ConditionOptions{ @@ -51,20 +52,28 @@ func TestEventRuleMatch(t *testing.T) { InverseMatch: true, }, }, + RoleNames: []dataprovider.ConditionPattern{ + { + Pattern: role, + }, + }, }, } res := eventManager.checkProviderEventMatch(conditions, EventParams{ Name: "user1", + Role: role, Event: "add", }) assert.False(t, res) res = eventManager.checkProviderEventMatch(conditions, EventParams{ Name: "user2", + Role: role, Event: "update", }) assert.True(t, res) res = eventManager.checkProviderEventMatch(conditions, EventParams{ Name: "user2", + Role: role, Event: "delete", }) assert.False(t, res) @@ -72,15 +81,24 @@ func TestEventRuleMatch(t *testing.T) { res = eventManager.checkProviderEventMatch(conditions, EventParams{ Name: "user2", Event: "update", + Role: role, ObjectType: "share", }) assert.False(t, res) res = eventManager.checkProviderEventMatch(conditions, EventParams{ Name: "user2", Event: "update", + Role: role, ObjectType: "api_key", }) assert.True(t, res) + res = eventManager.checkProviderEventMatch(conditions, EventParams{ + Name: "user2", + Event: "update", + Role: role + "1", + ObjectType: "api_key", + }) + assert.False(t, res) // now test fs events conditions = dataprovider.EventConditions{ FsEvents: []string{operationUpload, operationDownload}, @@ -93,6 +111,12 @@ func TestEventRuleMatch(t *testing.T) { Pattern: "tester*", }, }, + RoleNames: []dataprovider.ConditionPattern{ + { + Pattern: role, + InverseMatch: true, + }, + }, FsPaths: []dataprovider.ConditionPattern{ { Pattern: "*.txt", @@ -116,6 +140,10 @@ func TestEventRuleMatch(t *testing.T) { params.Event = operationDownload res = eventManager.checkFsEventMatch(conditions, params) assert.True(t, res) + params.Role = role + res = eventManager.checkFsEventMatch(conditions, params) + assert.False(t, res) + params.Role = "" params.Name = "name" res = eventManager.checkFsEventMatch(conditions, params) assert.False(t, res) @@ -195,6 +223,49 @@ func TestEventRuleMatch(t *testing.T) { } res = eventManager.checkFsEventMatch(conditions, params) assert.True(t, res) + // check user conditions + user := dataprovider.User{} + user.Username = "u1" + res = checkUserConditionOptions(&user, &dataprovider.ConditionOptions{}) + assert.True(t, res) + res = checkUserConditionOptions(&user, &dataprovider.ConditionOptions{ + Names: []dataprovider.ConditionPattern{ + { + Pattern: "user", + }, + }, + }) + assert.False(t, res) + res = checkUserConditionOptions(&user, &dataprovider.ConditionOptions{ + RoleNames: []dataprovider.ConditionPattern{ + { + Pattern: role, + }, + }, + }) + assert.False(t, res) + user.Role = role + res = checkUserConditionOptions(&user, &dataprovider.ConditionOptions{ + RoleNames: []dataprovider.ConditionPattern{ + { + Pattern: role, + }, + }, + }) + assert.True(t, res) + res = checkUserConditionOptions(&user, &dataprovider.ConditionOptions{ + GroupNames: []dataprovider.ConditionPattern{ + { + Pattern: "group", + }, + }, + RoleNames: []dataprovider.ConditionPattern{ + { + Pattern: role, + }, + }, + }) + assert.False(t, res) } func TestEventManager(t *testing.T) { diff --git a/internal/dataprovider/eventrule.go b/internal/dataprovider/eventrule.go index a25f00bf..0b8370bd 100644 --- a/internal/dataprovider/eventrule.go +++ b/internal/dataprovider/eventrule.go @@ -1021,6 +1021,8 @@ type ConditionOptions struct { Names []ConditionPattern `json:"names,omitempty"` // Group names GroupNames []ConditionPattern `json:"group_names,omitempty"` + // Role names + RoleNames []ConditionPattern `json:"role_names,omitempty"` // Virtual paths FsPaths []ConditionPattern `json:"fs_paths,omitempty"` Protocols []string `json:"protocols,omitempty"` @@ -1040,6 +1042,7 @@ func (f *ConditionOptions) getACopy() ConditionOptions { return ConditionOptions{ Names: cloneConditionPatterns(f.Names), GroupNames: cloneConditionPatterns(f.GroupNames), + RoleNames: cloneConditionPatterns(f.RoleNames), FsPaths: cloneConditionPatterns(f.FsPaths), Protocols: protocols, ProviderObjects: providerObjects, @@ -1050,21 +1053,19 @@ func (f *ConditionOptions) getACopy() ConditionOptions { } func (f *ConditionOptions) validate() error { - for _, name := range f.Names { - if err := name.validate(); err != nil { - return err - } + if err := validateConditionPatterns(f.Names); err != nil { + return err } - for _, name := range f.GroupNames { - if err := name.validate(); err != nil { - return err - } + if err := validateConditionPatterns(f.GroupNames); err != nil { + return err } - for _, fsPath := range f.FsPaths { - if err := fsPath.validate(); err != nil { - return err - } + if err := validateConditionPatterns(f.RoleNames); err != nil { + return err } + if err := validateConditionPatterns(f.FsPaths); err != nil { + return err + } + for _, p := range f.Protocols { if !util.Contains(SupportedRuleConditionProtocols, p) { return util.NewValidationError(fmt.Sprintf("unsupported rule condition protocol: %q", p)) @@ -1192,6 +1193,7 @@ func (c *EventConditions) validate(trigger int) error { c.ProviderEvents = nil c.Options.Names = nil c.Options.GroupNames = nil + c.Options.RoleNames = nil c.Options.FsPaths = nil c.Options.Protocols = nil c.Options.MinFileSize = 0 @@ -1201,6 +1203,7 @@ func (c *EventConditions) validate(trigger int) error { c.FsEvents = nil c.ProviderEvents = nil c.Options.GroupNames = nil + c.Options.RoleNames = nil c.Options.FsPaths = nil c.Options.Protocols = nil c.Options.MinFileSize = 0 @@ -1445,6 +1448,15 @@ func cloneConditionPatterns(patterns []ConditionPattern) []ConditionPattern { return res } +func validateConditionPatterns(patterns []ConditionPattern) error { + for _, name := range patterns { + if err := name.validate(); err != nil { + return err + } + } + return nil +} + // Task stores the state for a scheduled task type Task struct { Name string `json:"name"` diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index b6db0c9d..0afdaed5 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -1541,6 +1541,14 @@ func TestActionRuleRelations(t *testing.T) { Month: "*", }, }, + Options: dataprovider.ConditionOptions{ + RoleNames: []dataprovider.ConditionPattern{ + { + Pattern: "g*", + InverseMatch: true, + }, + }, + }, }, Actions: []dataprovider.EventAction{ { @@ -2008,6 +2016,16 @@ func TestEventRuleValidation(t *testing.T) { _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest) assert.NoError(t, err) assert.Contains(t, string(resp), "unsupported provider event") + rule.Conditions.ProviderEvents = []string{"add"} + rule.Conditions.Options.RoleNames = []dataprovider.ConditionPattern{ + { + Pattern: "", + }, + } + _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest) + assert.NoError(t, err) + assert.Contains(t, string(resp), "empty condition pattern not allowed") + rule.Conditions.Options.RoleNames = nil rule.Trigger = dataprovider.EventTriggerSchedule _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest) assert.NoError(t, err) @@ -2026,8 +2044,8 @@ func TestEventRuleValidation(t *testing.T) { Month: "*", }, } - _, _, err = httpdtest.AddEventRule(rule, http.StatusInternalServerError) - assert.NoError(t, err) + _, resp, err = httpdtest.AddEventRule(rule, http.StatusInternalServerError) + assert.NoError(t, err, string(resp)) } func TestUserTransferLimits(t *testing.T) { @@ -20180,6 +20198,12 @@ func TestWebEventRule(t *testing.T) { InverseMatch: true, }, }, + RoleNames: []dataprovider.ConditionPattern{ + { + Pattern: "g*", + InverseMatch: true, + }, + }, }, }, Actions: []dataprovider.EventAction{ @@ -20211,6 +20235,8 @@ func TestWebEventRule(t *testing.T) { form.Set("type_name_pattern0", "inverse") form.Set("group_name_pattern0", rule.Conditions.Options.GroupNames[0].Pattern) form.Set("type_group_name_pattern0", "inverse") + form.Set("role_name_pattern0", rule.Conditions.Options.RoleNames[0].Pattern) + form.Set("type_role_name_pattern0", "inverse") req, err = http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode()))) assert.NoError(t, err) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -20309,6 +20335,12 @@ func TestWebEventRule(t *testing.T) { InverseMatch: true, }, }, + RoleNames: []dataprovider.ConditionPattern{ + { + Pattern: "g*", + InverseMatch: true, + }, + }, FsPaths: []dataprovider.ConditionPattern{ { Pattern: "/subdir/*.txt", diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index 40bd42c5..da40f8d6 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -2169,7 +2169,7 @@ func getEventActionFromPostFields(r *http.Request) (dataprovider.BaseEventAction func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventConditions, error) { var schedules []dataprovider.Schedule - var names, groupNames, fsPaths []dataprovider.ConditionPattern + var names, groupNames, roleNames, fsPaths []dataprovider.ConditionPattern for k := range r.Form { if strings.HasPrefix(k, "schedule_hour") { hour := r.Form.Get(k) @@ -2208,6 +2208,17 @@ func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventCo }) } } + if strings.HasPrefix(k, "role_name_pattern") { + pattern := r.Form.Get(k) + if pattern != "" { + idx := strings.TrimPrefix(k, "role_name_pattern") + patternType := r.Form.Get(fmt.Sprintf("type_role_name_pattern%s", idx)) + roleNames = append(roleNames, dataprovider.ConditionPattern{ + Pattern: pattern, + InverseMatch: patternType == inversePatternType, + }) + } + } if strings.HasPrefix(k, "fs_path_pattern") { pattern := r.Form.Get(k) if pattern != "" { @@ -2235,6 +2246,7 @@ func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventCo Options: dataprovider.ConditionOptions{ Names: names, GroupNames: groupNames, + RoleNames: roleNames, FsPaths: fsPaths, Protocols: r.Form["fs_protocols"], ProviderObjects: r.Form["provider_objects"], diff --git a/internal/httpdtest/httpdtest.go b/internal/httpdtest/httpdtest.go index dd5c8aef..0de69574 100644 --- a/internal/httpdtest/httpdtest.go +++ b/internal/httpdtest/httpdtest.go @@ -1510,6 +1510,9 @@ func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions) if err := compareConditionPatternOptions(expected.GroupNames, actual.GroupNames); err != nil { return errors.New("condition group names mismatch") } + if err := compareConditionPatternOptions(expected.RoleNames, actual.RoleNames); err != nil { + return errors.New("condition role names mismatch") + } if err := compareConditionPatternOptions(expected.FsPaths, actual.FsPaths); err != nil { return errors.New("condition fs_paths mismatch") } diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 93ef9a3b..d710995c 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -6500,6 +6500,10 @@ components: type: array items: $ref: '#/components/schemas/ConditionPattern' + role_names: + type: array + items: + $ref: '#/components/schemas/ConditionPattern' fs_paths: type: array items: diff --git a/templates/webadmin/eventrule.html b/templates/webadmin/eventrule.html index 7e45da38..d5922786 100644 --- a/templates/webadmin/eventrule.html +++ b/templates/webadmin/eventrule.html @@ -291,6 +291,60 @@ along with this program. If not, see . +
+
+ Role name filters +
+
+
Shell-like pattern filters for role names. For example "role*"" will match role names starting with "role".
+
+
+ {{range $idx, $val := .Rule.Conditions.Options.RoleNames}} +
+
+ +
+
+ +
+
+ +
+
+ {{else}} +
+
+ +
+
+ +
+
+ +
+
+ {{end}} +
+
+ +
+ +
+
+
+
Path filters @@ -542,6 +596,36 @@ along with this program. If not, see . $(this).closest(".form_field_group_names_outer_row").remove(); }); + $("body").on("click", ".add_new_role_name_pattern_field_btn", function () { + var index = $(".form_field_role_names_outer").find(".form_field_role_names_outer_row").length; + while (document.getElementById("idRoleNamePattern"+index) != null){ + index++; + } + $(".form_field_role_names_outer").append(` +
+
+ +
+
+ +
+
+ +
+
+ `); + $("#idRoleNamePatternType"+index).selectpicker(); + }); + + $("body").on("click", ".remove_role_name_pattern_btn_frm_field", function () { + $(this).closest(".form_field_role_names_outer_row").remove(); + }); + $("body").on("click", ".add_new_fs_path_pattern_field_btn", function () { var index = $(".form_field_fs_paths_outer").find("form_field_fs_paths_outer_row").length; while (document.getElementById("idFsPathPattern"+index) != null){