mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-22 23:50:32 +00:00
5e5a09f164
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2267 lines
66 KiB
Go
2267 lines
66 KiB
Go
// Copyright (C) 2019-2023 Nicola Murino
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
// by the Free Software Foundation, version 3.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package common
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/klauspost/compress/zip"
|
|
"github.com/rs/xid"
|
|
"github.com/sftpgo/sdk"
|
|
sdkkms "github.com/sftpgo/sdk/kms"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
|
"github.com/drakkan/sftpgo/v2/internal/kms"
|
|
"github.com/drakkan/sftpgo/v2/internal/util"
|
|
"github.com/drakkan/sftpgo/v2/internal/vfs"
|
|
)
|
|
|
|
func TestEventRuleMatch(t *testing.T) {
|
|
role := "role1"
|
|
conditions := &dataprovider.EventConditions{
|
|
ProviderEvents: []string{"add", "update"},
|
|
Options: dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "user1",
|
|
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)
|
|
conditions.Options.ProviderObjects = []string{"api_key"}
|
|
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},
|
|
Options: dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "user*",
|
|
},
|
|
{
|
|
Pattern: "tester*",
|
|
},
|
|
},
|
|
RoleNames: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: role,
|
|
InverseMatch: true,
|
|
},
|
|
},
|
|
FsPaths: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "/**/*.txt",
|
|
},
|
|
},
|
|
Protocols: []string{ProtocolSFTP},
|
|
MinFileSize: 10,
|
|
MaxFileSize: 30,
|
|
},
|
|
}
|
|
params := EventParams{
|
|
Name: "tester4",
|
|
Event: operationDelete,
|
|
VirtualPath: "/path.txt",
|
|
Protocol: ProtocolSFTP,
|
|
ObjectName: "path.txt",
|
|
FileSize: 20,
|
|
}
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
params.Event = operationDownload
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.True(t, res)
|
|
params.Role = role
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
params.Role = ""
|
|
params.Name = "name"
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
params.Name = "user5"
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.True(t, res)
|
|
params.VirtualPath = "/sub/f.jpg"
|
|
params.ObjectName = path.Base(params.VirtualPath)
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
params.VirtualPath = "/sub/f.txt"
|
|
params.ObjectName = path.Base(params.VirtualPath)
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.True(t, res)
|
|
params.Protocol = ProtocolHTTP
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
params.Protocol = ProtocolSFTP
|
|
params.FileSize = 5
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
params.FileSize = 50
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
params.FileSize = 25
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.True(t, res)
|
|
// bad pattern
|
|
conditions.Options.Names = []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "[-]",
|
|
},
|
|
}
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
// check fs events with group name filters
|
|
conditions = &dataprovider.EventConditions{
|
|
FsEvents: []string{operationUpload, operationDownload},
|
|
Options: dataprovider.ConditionOptions{
|
|
GroupNames: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "group*",
|
|
},
|
|
{
|
|
Pattern: "testgroup*",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
params = EventParams{
|
|
Name: "user1",
|
|
Event: operationUpload,
|
|
}
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
params.Groups = []sdk.GroupMapping{
|
|
{
|
|
Name: "g1",
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
{
|
|
Name: "g2",
|
|
Type: sdk.GroupTypeSecondary,
|
|
},
|
|
}
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
assert.False(t, res)
|
|
params.Groups = []sdk.GroupMapping{
|
|
{
|
|
Name: "testgroup2",
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
{
|
|
Name: "g2",
|
|
Type: sdk.GroupTypeSecondary,
|
|
},
|
|
}
|
|
res = eventManager.checkFsEventMatch(conditions, ¶ms)
|
|
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)
|
|
res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{
|
|
IDPLoginEvent: 0,
|
|
}, &EventParams{
|
|
Event: IDPLoginAdmin,
|
|
})
|
|
assert.True(t, res)
|
|
res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{
|
|
IDPLoginEvent: 2,
|
|
}, &EventParams{
|
|
Event: IDPLoginAdmin,
|
|
})
|
|
assert.True(t, res)
|
|
res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{
|
|
IDPLoginEvent: 1,
|
|
}, &EventParams{
|
|
Event: IDPLoginAdmin,
|
|
})
|
|
assert.False(t, res)
|
|
res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{
|
|
IDPLoginEvent: 1,
|
|
}, &EventParams{
|
|
Event: IDPLoginUser,
|
|
})
|
|
assert.True(t, res)
|
|
res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{
|
|
IDPLoginEvent: 1,
|
|
}, &EventParams{
|
|
Name: "user",
|
|
Event: IDPLoginUser,
|
|
})
|
|
assert.True(t, res)
|
|
res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{
|
|
IDPLoginEvent: 1,
|
|
Options: dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "abc",
|
|
},
|
|
},
|
|
},
|
|
}, &EventParams{
|
|
Name: "user",
|
|
Event: IDPLoginUser,
|
|
})
|
|
assert.False(t, res)
|
|
res = eventManager.checkIPDLoginEventMatch(&dataprovider.EventConditions{
|
|
IDPLoginEvent: 2,
|
|
}, &EventParams{
|
|
Name: "user",
|
|
Event: IDPLoginUser,
|
|
})
|
|
assert.False(t, res)
|
|
}
|
|
|
|
func TestDoubleStarMatching(t *testing.T) {
|
|
c := dataprovider.ConditionPattern{
|
|
Pattern: "/mydir/**",
|
|
}
|
|
res := checkEventConditionPattern(c, "/mydir")
|
|
assert.True(t, res)
|
|
res = checkEventConditionPattern(c, "/mydirname")
|
|
assert.False(t, res)
|
|
res = checkEventConditionPattern(c, "/mydir/sub")
|
|
assert.True(t, res)
|
|
res = checkEventConditionPattern(c, "/mydir/sub/dir")
|
|
assert.True(t, res)
|
|
|
|
c.Pattern = "/**/*"
|
|
res = checkEventConditionPattern(c, "/mydir")
|
|
assert.True(t, res)
|
|
res = checkEventConditionPattern(c, "/mydirname")
|
|
assert.True(t, res)
|
|
res = checkEventConditionPattern(c, "/mydir/sub/dir/file.txt")
|
|
assert.True(t, res)
|
|
|
|
c.Pattern = "/mydir/**/*.txt"
|
|
res = checkEventConditionPattern(c, "/mydir")
|
|
assert.False(t, res)
|
|
res = checkEventConditionPattern(c, "/mydirname/f.txt")
|
|
assert.False(t, res)
|
|
res = checkEventConditionPattern(c, "/mydir/sub")
|
|
assert.False(t, res)
|
|
res = checkEventConditionPattern(c, "/mydir/sub/dir")
|
|
assert.False(t, res)
|
|
res = checkEventConditionPattern(c, "/mydir/sub/dir/a.txt")
|
|
assert.True(t, res)
|
|
|
|
c.InverseMatch = true
|
|
assert.True(t, checkEventConditionPattern(c, "/mydir"))
|
|
assert.True(t, checkEventConditionPattern(c, "/mydirname/f.txt"))
|
|
assert.True(t, checkEventConditionPattern(c, "/mydir/sub"))
|
|
assert.True(t, checkEventConditionPattern(c, "/mydir/sub/dir"))
|
|
assert.False(t, checkEventConditionPattern(c, "/mydir/sub/dir/a.txt"))
|
|
}
|
|
|
|
func TestMutlipleDoubleStarMatching(t *testing.T) {
|
|
patterns := []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "/**/*.txt",
|
|
InverseMatch: false,
|
|
},
|
|
{
|
|
Pattern: "/**/*.tmp",
|
|
InverseMatch: false,
|
|
},
|
|
}
|
|
assert.False(t, checkEventConditionPatterns("/mydir", patterns))
|
|
assert.True(t, checkEventConditionPatterns("/mydir/test.tmp", patterns))
|
|
assert.True(t, checkEventConditionPatterns("/mydir/test.txt", patterns))
|
|
assert.False(t, checkEventConditionPatterns("/mydir/test.csv", patterns))
|
|
assert.False(t, checkEventConditionPatterns("/mydir/sub", patterns))
|
|
assert.True(t, checkEventConditionPatterns("/mydir/sub/test.tmp", patterns))
|
|
assert.True(t, checkEventConditionPatterns("/mydir/sub/test.txt", patterns))
|
|
assert.False(t, checkEventConditionPatterns("/mydir/sub/test.csv", patterns))
|
|
}
|
|
|
|
func TestMultipleDoubleStarMatchingInverse(t *testing.T) {
|
|
patterns := []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "/**/*.txt",
|
|
InverseMatch: true,
|
|
},
|
|
{
|
|
Pattern: "/**/*.tmp",
|
|
InverseMatch: true,
|
|
},
|
|
}
|
|
assert.True(t, checkEventConditionPatterns("/mydir", patterns))
|
|
assert.False(t, checkEventConditionPatterns("/mydir/test.tmp", patterns))
|
|
assert.False(t, checkEventConditionPatterns("/mydir/test.txt", patterns))
|
|
assert.True(t, checkEventConditionPatterns("/mydir/test.csv", patterns))
|
|
assert.True(t, checkEventConditionPatterns("/mydir/sub", patterns))
|
|
assert.False(t, checkEventConditionPatterns("/mydir/sub/test.tmp", patterns))
|
|
assert.False(t, checkEventConditionPatterns("/mydir/sub/test.txt", patterns))
|
|
assert.True(t, checkEventConditionPatterns("/mydir/sub/test.csv", patterns))
|
|
}
|
|
|
|
func TestGroupConditionPatterns(t *testing.T) {
|
|
group1 := "group1"
|
|
group2 := "group2"
|
|
patterns := []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: group1,
|
|
},
|
|
{
|
|
Pattern: group2,
|
|
},
|
|
}
|
|
inversePatterns := []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: group1,
|
|
InverseMatch: true,
|
|
},
|
|
{
|
|
Pattern: group2,
|
|
InverseMatch: true,
|
|
},
|
|
}
|
|
groups := []sdk.GroupMapping{
|
|
{
|
|
Name: "group3",
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
}
|
|
assert.False(t, checkEventGroupConditionPatterns(groups, patterns))
|
|
assert.True(t, checkEventGroupConditionPatterns(groups, inversePatterns))
|
|
|
|
groups = []sdk.GroupMapping{
|
|
{
|
|
Name: group1,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
{
|
|
Name: "group4",
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
}
|
|
assert.True(t, checkEventGroupConditionPatterns(groups, patterns))
|
|
assert.False(t, checkEventGroupConditionPatterns(groups, inversePatterns))
|
|
groups = []sdk.GroupMapping{
|
|
{
|
|
Name: group1,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
}
|
|
assert.True(t, checkEventGroupConditionPatterns(groups, patterns))
|
|
assert.False(t, checkEventGroupConditionPatterns(groups, inversePatterns))
|
|
groups = []sdk.GroupMapping{
|
|
{
|
|
Name: "group11",
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
}
|
|
assert.False(t, checkEventGroupConditionPatterns(groups, patterns))
|
|
assert.True(t, checkEventGroupConditionPatterns(groups, inversePatterns))
|
|
}
|
|
|
|
func TestEventManager(t *testing.T) {
|
|
startEventScheduler()
|
|
action := &dataprovider.BaseEventAction{
|
|
Name: "test_action",
|
|
Type: dataprovider.ActionTypeHTTP,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
HTTPConfig: dataprovider.EventActionHTTPConfig{
|
|
Endpoint: "http://localhost",
|
|
Timeout: 20,
|
|
Method: http.MethodGet,
|
|
},
|
|
},
|
|
}
|
|
err := dataprovider.AddEventAction(action, "", "", "")
|
|
assert.NoError(t, err)
|
|
rule := &dataprovider.EventRule{
|
|
Name: "rule",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{operationUpload},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
err = dataprovider.AddEventRule(rule, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
eventManager.RLock()
|
|
assert.Len(t, eventManager.FsEvents, 1)
|
|
assert.Len(t, eventManager.ProviderEvents, 0)
|
|
assert.Len(t, eventManager.Schedules, 0)
|
|
assert.Len(t, eventManager.schedulesMapping, 0)
|
|
eventManager.RUnlock()
|
|
|
|
rule.Trigger = dataprovider.EventTriggerProviderEvent
|
|
rule.Conditions = dataprovider.EventConditions{
|
|
ProviderEvents: []string{"add"},
|
|
}
|
|
err = dataprovider.UpdateEventRule(rule, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
eventManager.RLock()
|
|
assert.Len(t, eventManager.FsEvents, 0)
|
|
assert.Len(t, eventManager.ProviderEvents, 1)
|
|
assert.Len(t, eventManager.Schedules, 0)
|
|
assert.Len(t, eventManager.schedulesMapping, 0)
|
|
eventManager.RUnlock()
|
|
|
|
rule.Trigger = dataprovider.EventTriggerSchedule
|
|
rule.Conditions = dataprovider.EventConditions{
|
|
Schedules: []dataprovider.Schedule{
|
|
{
|
|
Hours: "0",
|
|
DayOfWeek: "*",
|
|
DayOfMonth: "*",
|
|
Month: "*",
|
|
},
|
|
},
|
|
}
|
|
rule.DeletedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-12 * time.Hour))
|
|
eventManager.addUpdateRuleInternal(*rule)
|
|
|
|
eventManager.RLock()
|
|
assert.Len(t, eventManager.FsEvents, 0)
|
|
assert.Len(t, eventManager.ProviderEvents, 0)
|
|
assert.Len(t, eventManager.Schedules, 0)
|
|
assert.Len(t, eventManager.schedulesMapping, 0)
|
|
eventManager.RUnlock()
|
|
|
|
assert.Eventually(t, func() bool {
|
|
_, err = dataprovider.EventRuleExists(rule.Name)
|
|
ok := errors.Is(err, util.ErrNotFound)
|
|
return ok
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
|
|
rule.DeletedAt = 0
|
|
err = dataprovider.AddEventRule(rule, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
eventManager.RLock()
|
|
assert.Len(t, eventManager.FsEvents, 0)
|
|
assert.Len(t, eventManager.ProviderEvents, 0)
|
|
assert.Len(t, eventManager.Schedules, 1)
|
|
assert.Len(t, eventManager.schedulesMapping, 1)
|
|
eventManager.RUnlock()
|
|
|
|
err = dataprovider.DeleteEventRule(rule.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
eventManager.RLock()
|
|
assert.Len(t, eventManager.FsEvents, 0)
|
|
assert.Len(t, eventManager.ProviderEvents, 0)
|
|
assert.Len(t, eventManager.Schedules, 0)
|
|
assert.Len(t, eventManager.schedulesMapping, 0)
|
|
eventManager.RUnlock()
|
|
|
|
err = dataprovider.DeleteEventAction(action.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
stopEventScheduler()
|
|
}
|
|
|
|
func TestEventManagerErrors(t *testing.T) {
|
|
startEventScheduler()
|
|
providerConf := dataprovider.GetProviderConfig()
|
|
err := dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
|
|
params := EventParams{
|
|
sender: "sender",
|
|
}
|
|
_, err = params.getUsers()
|
|
assert.Error(t, err)
|
|
_, err = params.getFolders()
|
|
assert.Error(t, err)
|
|
|
|
err = executeUsersQuotaResetRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeFoldersQuotaResetRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeTransferQuotaResetRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeMetadataCheckRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeUserExpirationCheckRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeDeleteFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeMkdirFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeRenameFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeExistFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeCopyFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executeCompressFsRuleAction(dataprovider.EventActionFsCompress{}, nil, dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
err = executePwdExpirationCheckRuleAction(dataprovider.EventActionPasswordExpiration{},
|
|
dataprovider.ConditionOptions{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
_, err = executeAdminCheckAction(&dataprovider.EventActionIDPAccountCheck{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
_, err = executeUserCheckAction(&dataprovider.EventActionIDPAccountCheck{}, &EventParams{})
|
|
assert.Error(t, err)
|
|
|
|
groupName := "agroup"
|
|
err = executeQuotaResetForUser(&dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
err = executeMetadataCheckForUser(&dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
err = executeDataRetentionCheckForUser(dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
}, nil, &EventParams{}, "")
|
|
assert.Error(t, err)
|
|
err = executeDeleteFsActionForUser(nil, nil, dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
err = executeMkDirsFsActionForUser(nil, nil, dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
err = executeRenameFsActionForUser(nil, nil, dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
err = executeExistFsActionForUser(nil, nil, dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
err = executeCopyFsActionForUser(nil, nil, dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
err = executeCompressFsActionForUser(dataprovider.EventActionFsCompress{}, nil, dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
err = executePwdExpirationCheckForUser(&dataprovider.User{
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
}}, dataprovider.EventActionPasswordExpiration{})
|
|
assert.Error(t, err)
|
|
|
|
_, _, err = getHTTPRuleActionBody(&dataprovider.EventActionHTTPConfig{
|
|
Method: http.MethodPost,
|
|
Parts: []dataprovider.HTTPPart{
|
|
{
|
|
Name: "p1",
|
|
},
|
|
},
|
|
}, nil, nil, dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: "u",
|
|
},
|
|
Groups: []sdk.GroupMapping{
|
|
{
|
|
Name: groupName,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
},
|
|
}, &EventParams{}, false)
|
|
assert.Error(t, err)
|
|
|
|
dataRetentionAction := dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeDataRetentionCheck,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
RetentionConfig: dataprovider.EventActionDataRetentionConfig{
|
|
Folders: []dataprovider.FolderRetention{
|
|
{
|
|
Path: "/",
|
|
Retention: 24,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "username1",
|
|
},
|
|
},
|
|
})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "unable to get users")
|
|
}
|
|
|
|
eventManager.loadRules()
|
|
|
|
eventManager.RLock()
|
|
assert.Len(t, eventManager.FsEvents, 0)
|
|
assert.Len(t, eventManager.ProviderEvents, 0)
|
|
assert.Len(t, eventManager.Schedules, 0)
|
|
eventManager.RUnlock()
|
|
|
|
// rule with invalid trigger
|
|
eventManager.addUpdateRuleInternal(dataprovider.EventRule{
|
|
Name: "test rule",
|
|
Status: 1,
|
|
Trigger: -1,
|
|
})
|
|
|
|
eventManager.RLock()
|
|
assert.Len(t, eventManager.FsEvents, 0)
|
|
assert.Len(t, eventManager.ProviderEvents, 0)
|
|
assert.Len(t, eventManager.Schedules, 0)
|
|
eventManager.RUnlock()
|
|
// rule with invalid cronspec
|
|
eventManager.addUpdateRuleInternal(dataprovider.EventRule{
|
|
Name: "test rule",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerSchedule,
|
|
Conditions: dataprovider.EventConditions{
|
|
Schedules: []dataprovider.Schedule{
|
|
{
|
|
Hours: "1000",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
eventManager.RLock()
|
|
assert.Len(t, eventManager.FsEvents, 0)
|
|
assert.Len(t, eventManager.ProviderEvents, 0)
|
|
assert.Len(t, eventManager.Schedules, 0)
|
|
eventManager.RUnlock()
|
|
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
stopEventScheduler()
|
|
}
|
|
|
|
func TestEventRuleActions(t *testing.T) {
|
|
actionName := "test rule action"
|
|
action := dataprovider.BaseEventAction{
|
|
Name: actionName,
|
|
Type: dataprovider.ActionTypeBackup,
|
|
}
|
|
err := executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{})
|
|
assert.NoError(t, err)
|
|
action.Type = -1
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{})
|
|
assert.Error(t, err)
|
|
|
|
action = dataprovider.BaseEventAction{
|
|
Name: actionName,
|
|
Type: dataprovider.ActionTypeHTTP,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
HTTPConfig: dataprovider.EventActionHTTPConfig{
|
|
Endpoint: "http://foo\x7f.com/", // invalid URL
|
|
SkipTLSVerify: true,
|
|
Body: `"data": "{{ObjectData}}"`,
|
|
Method: http.MethodPost,
|
|
QueryParameters: []dataprovider.KeyValue{
|
|
{
|
|
Key: "param",
|
|
Value: "value",
|
|
},
|
|
},
|
|
Timeout: 5,
|
|
Headers: []dataprovider.KeyValue{
|
|
{
|
|
Key: "Content-Type",
|
|
Value: "application/json",
|
|
},
|
|
},
|
|
Username: "httpuser",
|
|
},
|
|
},
|
|
}
|
|
action.Options.SetEmptySecretsIfNil()
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "invalid endpoint")
|
|
}
|
|
action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v", httpAddr)
|
|
params := &EventParams{
|
|
Name: "a",
|
|
Object: &dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: "test user",
|
|
},
|
|
},
|
|
}
|
|
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
|
assert.NoError(t, err)
|
|
action.Options.HTTPConfig.Method = http.MethodGet
|
|
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
|
assert.NoError(t, err)
|
|
action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v/404", httpAddr)
|
|
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "unexpected status code: 404")
|
|
}
|
|
action.Options.HTTPConfig.Endpoint = "http://invalid:1234"
|
|
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
|
assert.Error(t, err)
|
|
action.Options.HTTPConfig.QueryParameters = nil
|
|
action.Options.HTTPConfig.Endpoint = "http://bar\x7f.com/"
|
|
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
|
assert.Error(t, err)
|
|
action.Options.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusSecretBox, "payload", "key", "data")
|
|
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "unable to decrypt HTTP password")
|
|
}
|
|
action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v", httpAddr)
|
|
action.Options.HTTPConfig.Password = kms.NewEmptySecret()
|
|
action.Options.HTTPConfig.Body = ""
|
|
action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
|
|
{
|
|
Name: "p1",
|
|
Filepath: "path",
|
|
},
|
|
}
|
|
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
|
|
assert.Contains(t, getErrorString(err), "error getting user")
|
|
|
|
action.Options.HTTPConfig.Parts = nil
|
|
action.Options.HTTPConfig.Body = "{{ObjectData}}"
|
|
// test disk and transfer quota reset
|
|
username1 := "user1"
|
|
username2 := "user2"
|
|
user1 := dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: username1,
|
|
HomeDir: filepath.Join(os.TempDir(), username1),
|
|
Status: 1,
|
|
Permissions: map[string][]string{
|
|
"/": {dataprovider.PermAny},
|
|
},
|
|
},
|
|
}
|
|
user2 := dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: username2,
|
|
HomeDir: filepath.Join(os.TempDir(), username2),
|
|
Status: 1,
|
|
Permissions: map[string][]string{
|
|
"/": {dataprovider.PermAny},
|
|
},
|
|
},
|
|
}
|
|
user2.Filters.PasswordExpiration = 10
|
|
err = dataprovider.AddUser(&user1, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.AddUser(&user2, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
err = executePwdExpirationCheckRuleAction(dataprovider.EventActionPasswordExpiration{
|
|
Threshold: 20,
|
|
}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: user2.Username,
|
|
},
|
|
},
|
|
}, &EventParams{})
|
|
// smtp not configured
|
|
assert.Error(t, err)
|
|
|
|
action = dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeUserQuotaReset,
|
|
}
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err) // no home dir
|
|
// create the home dir
|
|
err = os.MkdirAll(user1.GetHomeDir(), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(user1.GetHomeDir(), "file.txt"), []byte("user"), 0666)
|
|
assert.NoError(t, err)
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
userGet, err := dataprovider.UserExists(username1, "")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, userGet.UsedQuotaFiles)
|
|
assert.Equal(t, int64(4), userGet.UsedQuotaSize)
|
|
// simulate another quota scan in progress
|
|
assert.True(t, QuotaScans.AddUserQuotaScan(username1, ""))
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.True(t, QuotaScans.RemoveUserQuotaScan(username1))
|
|
// non matching pattern
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "don't match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no user quota reset executed")
|
|
|
|
action = dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeMetadataCheck,
|
|
}
|
|
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "don't match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no metadata check executed")
|
|
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
// simulate another metadata check in progress
|
|
assert.True(t, ActiveMetadataChecks.Add(username1, ""))
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.True(t, ActiveMetadataChecks.Remove(username1))
|
|
|
|
action = dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeUserExpirationCheck,
|
|
}
|
|
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "don't match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no user expiration check executed")
|
|
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
dataRetentionAction := dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeDataRetentionCheck,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
RetentionConfig: dataprovider.EventActionDataRetentionConfig{
|
|
Folders: []dataprovider.FolderRetention{
|
|
{
|
|
Path: "",
|
|
Retention: 24,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err) // invalid config, no folder path specified
|
|
retentionDir := "testretention"
|
|
dataRetentionAction = dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeDataRetentionCheck,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
RetentionConfig: dataprovider.EventActionDataRetentionConfig{
|
|
Folders: []dataprovider.FolderRetention{
|
|
{
|
|
Path: path.Join("/", retentionDir),
|
|
Retention: 24,
|
|
DeleteEmptyDirs: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
// create some test files
|
|
file1 := filepath.Join(user1.GetHomeDir(), "file1.txt")
|
|
file2 := filepath.Join(user1.GetHomeDir(), retentionDir, "file2.txt")
|
|
file3 := filepath.Join(user1.GetHomeDir(), retentionDir, "file3.txt")
|
|
file4 := filepath.Join(user1.GetHomeDir(), retentionDir, "sub", "file4.txt")
|
|
|
|
err = os.MkdirAll(filepath.Dir(file4), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
|
|
for _, f := range []string{file1, file2, file3, file4} {
|
|
err = os.WriteFile(f, []byte(""), 0666)
|
|
assert.NoError(t, err)
|
|
}
|
|
timeBeforeRetention := time.Now().Add(-48 * time.Hour)
|
|
err = os.Chtimes(file1, timeBeforeRetention, timeBeforeRetention)
|
|
assert.NoError(t, err)
|
|
err = os.Chtimes(file2, timeBeforeRetention, timeBeforeRetention)
|
|
assert.NoError(t, err)
|
|
err = os.Chtimes(file4, timeBeforeRetention, timeBeforeRetention)
|
|
assert.NoError(t, err)
|
|
|
|
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.FileExists(t, file1)
|
|
assert.NoFileExists(t, file2)
|
|
assert.FileExists(t, file3)
|
|
assert.NoDirExists(t, filepath.Dir(file4))
|
|
// simulate another check in progress
|
|
c := RetentionChecks.Add(RetentionCheck{}, &user1)
|
|
assert.NotNil(t, c)
|
|
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
RetentionChecks.remove(user1.Username)
|
|
|
|
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "no match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no retention check executed")
|
|
|
|
// test file exists action
|
|
action = dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionExist,
|
|
Exist: []string{"/file1.txt", path.Join("/", retentionDir, "file3.txt")},
|
|
},
|
|
},
|
|
}
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "no match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no existence check executed")
|
|
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
action.Options.FsConfig.Exist = []string{"/file1.txt", path.Join("/", retentionDir, "file2.txt")}
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
err = os.RemoveAll(user1.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.UpdateUserTransferQuota(&user1, 100, 100, true)
|
|
assert.NoError(t, err)
|
|
|
|
action.Type = dataprovider.ActionTypeTransferQuotaReset
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username1,
|
|
},
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
userGet, err = dataprovider.UserExists(username1, "")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), userGet.UsedDownloadDataTransfer)
|
|
assert.Equal(t, int64(0), userGet.UsedUploadDataTransfer)
|
|
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "no match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no transfer quota reset executed")
|
|
|
|
action.Type = dataprovider.ActionTypeFilesystem
|
|
action.Options = dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionRename,
|
|
Renames: []dataprovider.KeyValue{
|
|
{
|
|
Key: "/source",
|
|
Value: "/target",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "no match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no rename executed")
|
|
|
|
action.Options = dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionDelete,
|
|
Deletes: []string{"/dir1"},
|
|
},
|
|
}
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "no match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no delete executed")
|
|
|
|
action.Options = dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionMkdirs,
|
|
Deletes: []string{"/dir1"},
|
|
},
|
|
}
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "no match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no mkdir executed")
|
|
|
|
action.Options = dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionCompress,
|
|
Compress: dataprovider.EventActionFsCompress{
|
|
Name: "test.zip",
|
|
Paths: []string{"/{{VirtualPath}}"},
|
|
},
|
|
},
|
|
}
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "no match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no file/folder compressed")
|
|
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
GroupNames: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "no match",
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "no file/folder compressed")
|
|
|
|
err = dataprovider.DeleteUser(username1, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteUser(username2, "", "", "")
|
|
assert.NoError(t, err)
|
|
// test folder quota reset
|
|
foldername1 := "f1"
|
|
foldername2 := "f2"
|
|
folder1 := vfs.BaseVirtualFolder{
|
|
Name: foldername1,
|
|
MappedPath: filepath.Join(os.TempDir(), foldername1),
|
|
}
|
|
folder2 := vfs.BaseVirtualFolder{
|
|
Name: foldername2,
|
|
MappedPath: filepath.Join(os.TempDir(), foldername2),
|
|
}
|
|
err = dataprovider.AddFolder(&folder1, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.AddFolder(&folder2, "", "", "")
|
|
assert.NoError(t, err)
|
|
action = dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeFolderQuotaReset,
|
|
}
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: foldername1,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err) // no home dir
|
|
err = os.MkdirAll(folder1.MappedPath, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(folder1.MappedPath, "file.txt"), []byte("folder"), 0666)
|
|
assert.NoError(t, err)
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: foldername1,
|
|
},
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
folderGet, err := dataprovider.GetFolderByName(foldername1)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, folderGet.UsedQuotaFiles)
|
|
assert.Equal(t, int64(6), folderGet.UsedQuotaSize)
|
|
// simulate another quota scan in progress
|
|
assert.True(t, QuotaScans.AddVFolderQuotaScan(foldername1))
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: foldername1,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.True(t, QuotaScans.RemoveVFolderQuotaScan(foldername1))
|
|
|
|
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "no folder match",
|
|
},
|
|
},
|
|
})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no folder quota reset executed")
|
|
}
|
|
|
|
body, _, err := getHTTPRuleActionBody(&dataprovider.EventActionHTTPConfig{
|
|
Method: http.MethodPost,
|
|
}, nil, nil, dataprovider.User{}, &EventParams{}, true)
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, body)
|
|
body, _, err = getHTTPRuleActionBody(&dataprovider.EventActionHTTPConfig{
|
|
Method: http.MethodPost,
|
|
Body: "test body",
|
|
}, nil, nil, dataprovider.User{}, &EventParams{}, false)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, body)
|
|
|
|
err = os.RemoveAll(folder1.MappedPath)
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteFolder(foldername1, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteFolder(foldername2, "", "", "")
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestIDPAccountCheckRule(t *testing.T) {
|
|
_, _, err := executeIDPAccountCheckRule(dataprovider.EventRule{}, EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no action executed")
|
|
}
|
|
_, _, err = executeIDPAccountCheckRule(dataprovider.EventRule{
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: "n",
|
|
Type: dataprovider.ActionTypeIDPAccountCheck,
|
|
},
|
|
},
|
|
},
|
|
}, EventParams{Event: "invalid"})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "unsupported IDP login event")
|
|
}
|
|
// invalid json
|
|
_, err = executeAdminCheckAction(&dataprovider.EventActionIDPAccountCheck{TemplateAdmin: "{"}, &EventParams{Name: "missing admin"})
|
|
assert.Error(t, err)
|
|
_, err = executeUserCheckAction(&dataprovider.EventActionIDPAccountCheck{TemplateUser: "["}, &EventParams{Name: "missing user"})
|
|
assert.Error(t, err)
|
|
_, err = executeUserCheckAction(&dataprovider.EventActionIDPAccountCheck{TemplateUser: "{}"}, &EventParams{Name: "invalid user template"})
|
|
assert.ErrorIs(t, err, util.ErrValidation)
|
|
username := "u"
|
|
c := &dataprovider.EventActionIDPAccountCheck{
|
|
Mode: 1,
|
|
TemplateUser: `{"username":"` + username + `","status":1,"home_dir":"` + util.JSONEscape(filepath.Join(os.TempDir())) + `","permissions":{"/":["*"]}}`,
|
|
}
|
|
params := &EventParams{
|
|
Name: username,
|
|
Event: IDPLoginUser,
|
|
}
|
|
user, err := executeUserCheckAction(c, params)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, username, user.Username)
|
|
assert.Equal(t, 1, user.Status)
|
|
user.Status = 0
|
|
err = dataprovider.UpdateUser(user, "", "", "")
|
|
assert.NoError(t, err)
|
|
// the user is not changed
|
|
user, err = executeUserCheckAction(c, params)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, username, user.Username)
|
|
assert.Equal(t, 0, user.Status)
|
|
// change the mode, the user is now updated
|
|
c.Mode = 0
|
|
user, err = executeUserCheckAction(c, params)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, username, user.Username)
|
|
assert.Equal(t, 1, user.Status)
|
|
|
|
err = dataprovider.DeleteUser(username, "", "", "")
|
|
assert.NoError(t, err)
|
|
// check rule consistency
|
|
r := dataprovider.EventRule{
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeIDPAccountCheck,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
err = r.CheckActionsConsistency("")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "IDP account check action is only supported for IDP login trigger")
|
|
}
|
|
r.Trigger = dataprovider.EventTriggerIDPLogin
|
|
err = r.CheckActionsConsistency("")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "IDP account check must be a sync action")
|
|
}
|
|
r.Actions[0].Options.ExecuteSync = true
|
|
err = r.CheckActionsConsistency("")
|
|
assert.NoError(t, err)
|
|
r.Actions = append(r.Actions, dataprovider.EventAction{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeCommand,
|
|
},
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
Order: 2,
|
|
})
|
|
err = r.CheckActionsConsistency("")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "IDP account check must be the only sync action")
|
|
}
|
|
}
|
|
|
|
func TestUserExpirationCheck(t *testing.T) {
|
|
username := "test_user_expiration_check"
|
|
user := dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: username,
|
|
Permissions: map[string][]string{
|
|
"/": {dataprovider.PermAny},
|
|
},
|
|
HomeDir: filepath.Join(os.TempDir(), username),
|
|
ExpirationDate: util.GetTimeAsMsSinceEpoch(time.Now().Add(-24 * time.Hour)),
|
|
},
|
|
}
|
|
user.Filters.PasswordExpiration = 5
|
|
err := dataprovider.AddUser(&user, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
conditions := dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username,
|
|
},
|
|
},
|
|
}
|
|
err = executeUserExpirationCheckRuleAction(conditions, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "expired users")
|
|
}
|
|
// the check will be skipped, the user is expired
|
|
err = executePwdExpirationCheckRuleAction(dataprovider.EventActionPasswordExpiration{Threshold: 10}, conditions, &EventParams{})
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.DeleteUser(username, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleActionsNoGroupMatching(t *testing.T) {
|
|
username := "test_user_action_group_matching"
|
|
user := dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: username,
|
|
Permissions: map[string][]string{
|
|
"/": {dataprovider.PermAny},
|
|
},
|
|
HomeDir: filepath.Join(os.TempDir(), username),
|
|
},
|
|
}
|
|
err := dataprovider.AddUser(&user, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
conditions := dataprovider.ConditionOptions{
|
|
GroupNames: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "agroup",
|
|
},
|
|
},
|
|
}
|
|
err = executeDeleteFsRuleAction(nil, nil, conditions, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no delete executed")
|
|
}
|
|
err = executeMkdirFsRuleAction(nil, nil, conditions, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no mkdir executed")
|
|
}
|
|
err = executeRenameFsRuleAction(nil, nil, conditions, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no rename executed")
|
|
}
|
|
err = executeExistFsRuleAction(nil, nil, conditions, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no existence check executed")
|
|
}
|
|
err = executeCopyFsRuleAction(nil, nil, conditions, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no copy executed")
|
|
}
|
|
err = executeUsersQuotaResetRuleAction(conditions, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no user quota reset executed")
|
|
}
|
|
err = executeMetadataCheckRuleAction(conditions, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no metadata check executed")
|
|
}
|
|
err = executeTransferQuotaResetRuleAction(conditions, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no transfer quota reset executed")
|
|
}
|
|
err = executeDataRetentionCheckRuleAction(dataprovider.EventActionDataRetentionConfig{}, conditions, &EventParams{}, "")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "no retention check executed")
|
|
}
|
|
|
|
err = dataprovider.DeleteUser(username, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestGetFileContent(t *testing.T) {
|
|
username := "test_user_get_file_content"
|
|
user := dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: username,
|
|
Permissions: map[string][]string{
|
|
"/": {dataprovider.PermAny},
|
|
},
|
|
HomeDir: filepath.Join(os.TempDir(), username),
|
|
},
|
|
}
|
|
err := dataprovider.AddUser(&user, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.MkdirAll(user.GetHomeDir(), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
fileContent := []byte("test file content")
|
|
err = os.WriteFile(filepath.Join(user.GetHomeDir(), "file.txt"), fileContent, 0666)
|
|
assert.NoError(t, err)
|
|
conn := NewBaseConnection(xid.New().String(), protocolEventAction, "", "", user)
|
|
replacer := strings.NewReplacer("old", "new")
|
|
files, err := getMailAttachments(conn, []string{"/file.txt"}, replacer)
|
|
assert.NoError(t, err)
|
|
if assert.Len(t, files, 1) {
|
|
var b bytes.Buffer
|
|
_, err = files[0].Writer(&b)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, fileContent, b.Bytes())
|
|
}
|
|
// missing file
|
|
_, err = getMailAttachments(conn, []string{"/file1.txt"}, replacer)
|
|
assert.Error(t, err)
|
|
// directory
|
|
_, err = getMailAttachments(conn, []string{"/"}, replacer)
|
|
assert.Error(t, err)
|
|
// files too large
|
|
content := make([]byte, maxAttachmentsSize/2+1)
|
|
_, err = rand.Read(content)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(user.GetHomeDir(), "file1.txt"), content, 0666)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(user.GetHomeDir(), "file2.txt"), content, 0666)
|
|
assert.NoError(t, err)
|
|
files, err = getMailAttachments(conn, []string{"/file1.txt"}, replacer)
|
|
assert.NoError(t, err)
|
|
if assert.Len(t, files, 1) {
|
|
var b bytes.Buffer
|
|
_, err = files[0].Writer(&b)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, content, b.Bytes())
|
|
}
|
|
_, err = getMailAttachments(conn, []string{"/file1.txt", "/file2.txt"}, replacer)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "size too large")
|
|
}
|
|
// change the filesystem provider
|
|
user.FsConfig.Provider = sdk.CryptedFilesystemProvider
|
|
user.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret("pwd")
|
|
err = dataprovider.UpdateUser(&user, "", "", "")
|
|
assert.NoError(t, err)
|
|
conn = NewBaseConnection(xid.New().String(), protocolEventAction, "", "", user)
|
|
// the file is not encrypted so reading the encryption header will fail
|
|
files, err = getMailAttachments(conn, []string{"/file.txt"}, replacer)
|
|
assert.NoError(t, err)
|
|
if assert.Len(t, files, 1) {
|
|
var b bytes.Buffer
|
|
_, err = files[0].Writer(&b)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
err = dataprovider.DeleteUser(username, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestFilesystemActionErrors(t *testing.T) {
|
|
err := executeFsRuleAction(dataprovider.EventActionFilesystemConfig{}, dataprovider.ConditionOptions{}, &EventParams{})
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "unsupported filesystem action")
|
|
}
|
|
username := "test_user_for_actions"
|
|
testReplacer := strings.NewReplacer("old", "new")
|
|
user := dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: username,
|
|
Permissions: map[string][]string{
|
|
"/": {dataprovider.PermAny},
|
|
},
|
|
HomeDir: filepath.Join(os.TempDir(), username),
|
|
},
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.SFTPFilesystemProvider,
|
|
SFTPConfig: vfs.SFTPFsConfig{
|
|
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
|
|
Endpoint: "127.0.0.1:4022",
|
|
Username: username,
|
|
},
|
|
Password: kms.NewPlainSecret("pwd"),
|
|
},
|
|
},
|
|
}
|
|
err = executeEmailRuleAction(dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.net"},
|
|
Subject: "subject",
|
|
Body: "body",
|
|
Attachments: []string{"/file.txt"},
|
|
}, &EventParams{
|
|
sender: username,
|
|
})
|
|
assert.Error(t, err)
|
|
conn := NewBaseConnection("", protocolEventAction, "", "", user)
|
|
err = executeDeleteFileFsAction(conn, "", nil)
|
|
assert.Error(t, err)
|
|
err = dataprovider.AddUser(&user, "", "", "")
|
|
assert.NoError(t, err)
|
|
// check root fs fails
|
|
err = executeDeleteFsActionForUser(nil, testReplacer, user)
|
|
assert.Error(t, err)
|
|
err = executeMkDirsFsActionForUser(nil, testReplacer, user)
|
|
assert.Error(t, err)
|
|
err = executeRenameFsActionForUser(nil, testReplacer, user)
|
|
assert.Error(t, err)
|
|
err = executeExistFsActionForUser(nil, testReplacer, user)
|
|
assert.Error(t, err)
|
|
err = executeCopyFsActionForUser(nil, testReplacer, user)
|
|
assert.Error(t, err)
|
|
err = executeCompressFsActionForUser(dataprovider.EventActionFsCompress{}, testReplacer, user)
|
|
assert.Error(t, err)
|
|
_, _, _, _, err = getFileWriter(conn, "/path.txt", -1) //nolint:dogsled
|
|
assert.Error(t, err)
|
|
err = executeEmailRuleAction(dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.net"},
|
|
Subject: "subject",
|
|
Body: "body",
|
|
Attachments: []string{"/file1.txt"},
|
|
}, &EventParams{
|
|
sender: username,
|
|
})
|
|
assert.Error(t, err)
|
|
fn := getFileContentFn(NewBaseConnection("", protocolEventAction, "", "", user), "/f.txt", 1234)
|
|
var b bytes.Buffer
|
|
_, err = fn(&b)
|
|
assert.Error(t, err)
|
|
err = executeHTTPRuleAction(dataprovider.EventActionHTTPConfig{
|
|
Endpoint: "http://127.0.0.1:9999/",
|
|
Method: http.MethodPost,
|
|
Parts: []dataprovider.HTTPPart{
|
|
{
|
|
Name: "p1",
|
|
Filepath: "/filepath",
|
|
},
|
|
},
|
|
}, &EventParams{
|
|
sender: username,
|
|
})
|
|
assert.Error(t, err)
|
|
user.FsConfig.Provider = sdk.LocalFilesystemProvider
|
|
user.Permissions["/"] = []string{dataprovider.PermUpload}
|
|
err = dataprovider.DeleteUser(username, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.AddUser(&user, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = executeRenameFsActionForUser([]dataprovider.KeyValue{
|
|
{
|
|
Key: "/p1",
|
|
Value: "/p1",
|
|
},
|
|
}, testReplacer, user)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "the rename source and target cannot be the same")
|
|
}
|
|
err = executeRuleAction(dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionRename,
|
|
Renames: []dataprovider.KeyValue{
|
|
{
|
|
Key: "/p2",
|
|
Value: "/p2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
if runtime.GOOS != osWindows {
|
|
dirPath := filepath.Join(user.HomeDir, "adir", "sub")
|
|
err := os.MkdirAll(dirPath, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
filePath := filepath.Join(dirPath, "f.dat")
|
|
err = os.WriteFile(filePath, []byte("test file content"), 0666)
|
|
assert.NoError(t, err)
|
|
err = os.Chmod(dirPath, 0001)
|
|
assert.NoError(t, err)
|
|
|
|
err = executeDeleteFsActionForUser([]string{"/adir/sub"}, testReplacer, user)
|
|
assert.Error(t, err)
|
|
err = executeDeleteFsActionForUser([]string{"/adir/sub/f.dat"}, testReplacer, user)
|
|
assert.Error(t, err)
|
|
err = os.Chmod(dirPath, 0555)
|
|
assert.NoError(t, err)
|
|
err = executeDeleteFsActionForUser([]string{"/adir/sub/f.dat"}, testReplacer, user)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "unable to remove file")
|
|
}
|
|
err = executeRuleAction(dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionDelete,
|
|
Deletes: []string{"/adir/sub/f.dat"},
|
|
},
|
|
},
|
|
}, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
err = executeMkDirsFsActionForUser([]string{"/adir/sub/sub"}, testReplacer, user)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "unable to create dir")
|
|
}
|
|
err = executeMkDirsFsActionForUser([]string{"/adir/sub/sub/sub"}, testReplacer, user)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "unable to check parent dirs")
|
|
}
|
|
|
|
err = executeRuleAction(dataprovider.BaseEventAction{
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionMkdirs,
|
|
MkDirs: []string{"/adir/sub/sub1"},
|
|
},
|
|
},
|
|
}, &EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
err = os.Chmod(dirPath, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
|
|
conn = NewBaseConnection("", protocolEventAction, "", "", user)
|
|
wr := &zipWriterWrapper{
|
|
Name: "test.zip",
|
|
Writer: zip.NewWriter(bytes.NewBuffer(nil)),
|
|
Entries: map[string]bool{},
|
|
}
|
|
err = addZipEntry(wr, conn, "/adir/sub/f.dat", "/adir/sub/sub")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, getErrorString(err), "is outside base dir")
|
|
}
|
|
|
|
err = dataprovider.DeleteUser(username, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestQuotaActionsWithQuotaTrackDisabled(t *testing.T) {
|
|
oldProviderConf := dataprovider.GetProviderConfig()
|
|
providerConf := dataprovider.GetProviderConfig()
|
|
providerConf.TrackQuota = 0
|
|
err := dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
|
|
username := "u1"
|
|
user := dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: username,
|
|
HomeDir: filepath.Join(os.TempDir(), username),
|
|
Status: 1,
|
|
Permissions: map[string][]string{
|
|
"/": {dataprovider.PermAny},
|
|
},
|
|
},
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.LocalFilesystemProvider,
|
|
},
|
|
}
|
|
err = dataprovider.AddUser(&user, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
err = os.MkdirAll(user.GetHomeDir(), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeUserQuotaReset},
|
|
&EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeTransferQuotaReset},
|
|
&EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: username,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteUser(username, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
foldername := "f1"
|
|
folder := vfs.BaseVirtualFolder{
|
|
Name: foldername,
|
|
MappedPath: filepath.Join(os.TempDir(), foldername),
|
|
}
|
|
err = dataprovider.AddFolder(&folder, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.MkdirAll(folder.MappedPath, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
|
|
err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeFolderQuotaReset},
|
|
&EventParams{}, dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: foldername,
|
|
},
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
err = os.RemoveAll(folder.MappedPath)
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteFolder(foldername, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
err = dataprovider.Initialize(oldProviderConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestScheduledActions(t *testing.T) {
|
|
startEventScheduler()
|
|
backupsPath := filepath.Join(os.TempDir(), "backups")
|
|
err := os.RemoveAll(backupsPath)
|
|
assert.NoError(t, err)
|
|
|
|
action := &dataprovider.BaseEventAction{
|
|
Name: "action",
|
|
Type: dataprovider.ActionTypeBackup,
|
|
}
|
|
err = dataprovider.AddEventAction(action, "", "", "")
|
|
assert.NoError(t, err)
|
|
rule := &dataprovider.EventRule{
|
|
Name: "rule",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerSchedule,
|
|
Conditions: dataprovider.EventConditions{
|
|
Schedules: []dataprovider.Schedule{
|
|
{
|
|
Hours: "11",
|
|
DayOfWeek: "*",
|
|
DayOfMonth: "*",
|
|
Month: "*",
|
|
},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
job := eventCronJob{
|
|
ruleName: rule.Name,
|
|
}
|
|
job.Run() // rule not found
|
|
assert.NoDirExists(t, backupsPath)
|
|
|
|
err = dataprovider.AddEventRule(rule, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
job.Run()
|
|
assert.DirExists(t, backupsPath)
|
|
|
|
action.Type = dataprovider.ActionTypeEmail
|
|
action.Options = dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"example@example.com"},
|
|
Subject: "test with attachments",
|
|
Body: "body",
|
|
Attachments: []string{"/file1.txt"},
|
|
},
|
|
}
|
|
err = dataprovider.UpdateEventAction(action, "", "", "")
|
|
assert.NoError(t, err)
|
|
job.Run() // action is not compatible with a scheduled rule
|
|
|
|
err = dataprovider.DeleteEventRule(rule.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteEventAction(action.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(backupsPath)
|
|
assert.NoError(t, err)
|
|
stopEventScheduler()
|
|
}
|
|
|
|
func TestEventParamsCopy(t *testing.T) {
|
|
params := EventParams{
|
|
Name: "name",
|
|
Event: "event",
|
|
Status: 1,
|
|
errors: []string{"error1"},
|
|
retentionChecks: []executedRetentionCheck{},
|
|
}
|
|
paramsCopy := params.getACopy()
|
|
assert.Equal(t, params, *paramsCopy)
|
|
params.Name = "name mod"
|
|
paramsCopy.Event = "event mod"
|
|
paramsCopy.Status = 2
|
|
params.errors = append(params.errors, "error2")
|
|
paramsCopy.errors = append(paramsCopy.errors, "error3")
|
|
assert.Equal(t, []string{"error1", "error3"}, paramsCopy.errors)
|
|
assert.Equal(t, []string{"error1", "error2"}, params.errors)
|
|
assert.Equal(t, "name mod", params.Name)
|
|
assert.Equal(t, "name", paramsCopy.Name)
|
|
assert.Equal(t, "event", params.Event)
|
|
assert.Equal(t, "event mod", paramsCopy.Event)
|
|
assert.Equal(t, 1, params.Status)
|
|
assert.Equal(t, 2, paramsCopy.Status)
|
|
params = EventParams{
|
|
retentionChecks: []executedRetentionCheck{
|
|
{
|
|
Username: "u",
|
|
ActionName: "a",
|
|
Results: []folderRetentionCheckResult{
|
|
{
|
|
Path: "p",
|
|
Retention: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
paramsCopy = params.getACopy()
|
|
require.Len(t, paramsCopy.retentionChecks, 1)
|
|
paramsCopy.retentionChecks[0].Username = "u_copy"
|
|
paramsCopy.retentionChecks[0].ActionName = "a_copy"
|
|
require.Len(t, paramsCopy.retentionChecks[0].Results, 1)
|
|
paramsCopy.retentionChecks[0].Results[0].Path = "p_copy"
|
|
paramsCopy.retentionChecks[0].Results[0].Retention = 2
|
|
assert.Equal(t, "u", params.retentionChecks[0].Username)
|
|
assert.Equal(t, "a", params.retentionChecks[0].ActionName)
|
|
assert.Equal(t, "p", params.retentionChecks[0].Results[0].Path)
|
|
assert.Equal(t, 1, params.retentionChecks[0].Results[0].Retention)
|
|
assert.Equal(t, "u_copy", paramsCopy.retentionChecks[0].Username)
|
|
assert.Equal(t, "a_copy", paramsCopy.retentionChecks[0].ActionName)
|
|
assert.Equal(t, "p_copy", paramsCopy.retentionChecks[0].Results[0].Path)
|
|
assert.Equal(t, 2, paramsCopy.retentionChecks[0].Results[0].Retention)
|
|
assert.Nil(t, params.IDPCustomFields)
|
|
params.addIDPCustomFields(nil)
|
|
assert.Nil(t, params.IDPCustomFields)
|
|
params.IDPCustomFields = &map[string]string{
|
|
"field1": "val1",
|
|
}
|
|
paramsCopy = params.getACopy()
|
|
for k, v := range *paramsCopy.IDPCustomFields {
|
|
assert.Equal(t, "field1", k)
|
|
assert.Equal(t, "val1", v)
|
|
}
|
|
assert.Equal(t, params.IDPCustomFields, paramsCopy.IDPCustomFields)
|
|
(*paramsCopy.IDPCustomFields)["field1"] = "val2"
|
|
assert.NotEqual(t, params.IDPCustomFields, paramsCopy.IDPCustomFields)
|
|
}
|
|
|
|
func TestEventParamsStatusFromError(t *testing.T) {
|
|
params := EventParams{Status: 1}
|
|
params.AddError(os.ErrNotExist)
|
|
assert.Equal(t, 1, params.Status)
|
|
|
|
params = EventParams{Status: 1, updateStatusFromError: true}
|
|
params.AddError(os.ErrNotExist)
|
|
assert.Equal(t, 2, params.Status)
|
|
}
|
|
|
|
type testWriter struct {
|
|
errTest error
|
|
sentinel string
|
|
}
|
|
|
|
func (w *testWriter) Write(p []byte) (int, error) {
|
|
if w.errTest != nil {
|
|
return 0, w.errTest
|
|
}
|
|
if w.sentinel == string(p) {
|
|
return 0, io.ErrUnexpectedEOF
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func TestWriteHTTPPartsError(t *testing.T) {
|
|
m := multipart.NewWriter(&testWriter{
|
|
errTest: io.ErrShortWrite,
|
|
})
|
|
|
|
err := writeHTTPPart(m, dataprovider.HTTPPart{}, nil, nil, nil, &EventParams{}, false)
|
|
assert.ErrorIs(t, err, io.ErrShortWrite)
|
|
|
|
body := "test body"
|
|
m = multipart.NewWriter(&testWriter{sentinel: body})
|
|
err = writeHTTPPart(m, dataprovider.HTTPPart{
|
|
Body: body,
|
|
}, nil, nil, nil, &EventParams{}, false)
|
|
assert.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
|
}
|
|
|
|
func TestReplacePathsPlaceholders(t *testing.T) {
|
|
replacer := strings.NewReplacer("{{VirtualPath}}", "/path1")
|
|
paths := []string{"{{VirtualPath}}", "/path1"}
|
|
paths = replacePathsPlaceholders(paths, replacer)
|
|
assert.Equal(t, []string{"/path1"}, paths)
|
|
paths = []string{"{{VirtualPath}}", "/path2"}
|
|
paths = replacePathsPlaceholders(paths, replacer)
|
|
assert.Equal(t, []string{"/path1", "/path2"}, paths)
|
|
}
|
|
|
|
func TestEstimateZipSizeErrors(t *testing.T) {
|
|
u := dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: "u",
|
|
HomeDir: filepath.Join(os.TempDir(), "u"),
|
|
Status: 1,
|
|
Permissions: map[string][]string{
|
|
"/": {dataprovider.PermAny},
|
|
},
|
|
QuotaSize: 1000,
|
|
},
|
|
}
|
|
err := dataprovider.AddUser(&u, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.MkdirAll(u.GetHomeDir(), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
conn := NewBaseConnection("", ProtocolFTP, "", "", u)
|
|
_, _, _, _, err = getFileWriter(conn, "/missing/path/file.txt", -1) //nolint:dogsled
|
|
assert.Error(t, err)
|
|
_, err = getSizeForPath(conn, "/missing", vfs.NewFileInfo("missing", true, 0, time.Now(), false))
|
|
assert.True(t, conn.IsNotExistError(err))
|
|
if runtime.GOOS != osWindows {
|
|
err = os.MkdirAll(filepath.Join(u.HomeDir, "d1", "d2", "sub"), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(u.HomeDir, "d1", "d2", "sub", "file.txt"), []byte("data"), 0666)
|
|
assert.NoError(t, err)
|
|
err = os.Chmod(filepath.Join(u.HomeDir, "d1", "d2"), 0001)
|
|
assert.NoError(t, err)
|
|
size, err := estimateZipSize(conn, "/archive.zip", []string{"/d1"})
|
|
assert.Error(t, err, "size %d", size)
|
|
err = os.Chmod(filepath.Join(u.HomeDir, "d1", "d2"), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
}
|
|
err = dataprovider.DeleteUser(u.Username, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(u.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestOnDemandRule(t *testing.T) {
|
|
a := &dataprovider.BaseEventAction{
|
|
Name: "a",
|
|
Type: dataprovider.ActionTypeBackup,
|
|
Options: dataprovider.BaseEventActionOptions{},
|
|
}
|
|
err := dataprovider.AddEventAction(a, "", "", "")
|
|
assert.NoError(t, err)
|
|
r := &dataprovider.EventRule{
|
|
Name: "test on demand rule",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerOnDemand,
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: a.Name,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
err = dataprovider.AddEventRule(r, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
err = RunOnDemandRule(r.Name)
|
|
assert.NoError(t, err)
|
|
|
|
r.Status = 0
|
|
err = dataprovider.UpdateEventRule(r, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = RunOnDemandRule(r.Name)
|
|
assert.ErrorIs(t, err, util.ErrValidation)
|
|
assert.Contains(t, err.Error(), "is inactive")
|
|
|
|
r.Status = 1
|
|
r.Trigger = dataprovider.EventTriggerCertificate
|
|
err = dataprovider.UpdateEventRule(r, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = RunOnDemandRule(r.Name)
|
|
assert.ErrorIs(t, err, util.ErrValidation)
|
|
assert.Contains(t, err.Error(), "is not defined as on-demand")
|
|
|
|
a1 := &dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"example@example.org"},
|
|
Subject: "subject",
|
|
Body: "body",
|
|
Attachments: []string{"/{{VirtualPath}}"},
|
|
},
|
|
},
|
|
}
|
|
err = dataprovider.AddEventAction(a1, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
r.Trigger = dataprovider.EventTriggerOnDemand
|
|
r.Actions = []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: a1.Name,
|
|
},
|
|
},
|
|
}
|
|
err = dataprovider.UpdateEventRule(r, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = RunOnDemandRule(r.Name)
|
|
assert.ErrorIs(t, err, util.ErrValidation)
|
|
assert.Contains(t, err.Error(), "incosistent actions")
|
|
|
|
err = dataprovider.DeleteEventRule(r.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteEventAction(a.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteEventAction(a1.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
err = RunOnDemandRule(r.Name)
|
|
assert.ErrorIs(t, err, util.ErrNotFound)
|
|
}
|
|
|
|
func getErrorString(err error) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
return err.Error()
|
|
}
|
|
|
|
func TestHTTPEndpointWithPlaceholders(t *testing.T) {
|
|
c := dataprovider.EventActionHTTPConfig{
|
|
Endpoint: "http://127.0.0.1:8080/base/url/{{Name}}/{{VirtualPath}}/upload",
|
|
QueryParameters: []dataprovider.KeyValue{
|
|
{
|
|
Key: "u",
|
|
Value: "{{Name}}",
|
|
},
|
|
{
|
|
Key: "p",
|
|
Value: "{{VirtualPath}}",
|
|
},
|
|
},
|
|
}
|
|
name := "uname"
|
|
vPath := "/a dir/@ file.txt"
|
|
replacer := strings.NewReplacer("{{Name}}", name, "{{VirtualPath}}", vPath)
|
|
u, err := getHTTPRuleActionEndpoint(&c, replacer)
|
|
assert.NoError(t, err)
|
|
expected := "http://127.0.0.1:8080/base/url/" + url.PathEscape(name) + "/" + url.PathEscape(vPath) +
|
|
"/upload?" + "p=" + url.QueryEscape(vPath) + "&u=" + url.QueryEscape(name)
|
|
assert.Equal(t, expected, u)
|
|
|
|
c.Endpoint = "http://127.0.0.1/upload"
|
|
u, err = getHTTPRuleActionEndpoint(&c, replacer)
|
|
assert.NoError(t, err)
|
|
expected = c.Endpoint + "?p=" + url.QueryEscape(vPath) + "&u=" + url.QueryEscape(name)
|
|
assert.Equal(t, expected, u)
|
|
}
|