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