mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 15:10:23 +00:00
EventManager: add datetime placeholder
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
0c470b9202
commit
4103344989
12 changed files with 70 additions and 29 deletions
|
@ -673,7 +673,7 @@ func (c *Configuration) notifyCertificateRenewal(domain string, err error) {
|
|||
params := common.EventParams{
|
||||
Name: domain,
|
||||
Event: "Certificate renewal",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
if err != nil {
|
||||
params.Status = 2
|
||||
|
|
|
@ -92,8 +92,9 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
|
|||
if !hasHook && !hasNotifiersPlugin && !hasRules {
|
||||
return 0, nil
|
||||
}
|
||||
dateTime := time.Now()
|
||||
event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "",
|
||||
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil), 0, nil)
|
||||
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil), 0, dateTime, nil)
|
||||
if hasNotifiersPlugin {
|
||||
plugin.Handler.NotifyFsEvent(event)
|
||||
}
|
||||
|
@ -113,7 +114,7 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
|
|||
Protocol: event.Protocol,
|
||||
IP: event.IP,
|
||||
Role: event.Role,
|
||||
Timestamp: event.Timestamp,
|
||||
Timestamp: dateTime,
|
||||
Email: conn.User.Email,
|
||||
Object: nil,
|
||||
}
|
||||
|
@ -138,8 +139,9 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
|
|||
if !hasHook && !hasNotifiersPlugin && !hasRules {
|
||||
return nil
|
||||
}
|
||||
dateTime := time.Now()
|
||||
notification := newActionNotification(&conn.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd,
|
||||
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err), elapsed, metadata)
|
||||
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err), elapsed, dateTime, metadata)
|
||||
if hasNotifiersPlugin {
|
||||
plugin.Handler.NotifyFsEvent(notification)
|
||||
}
|
||||
|
@ -160,7 +162,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
|
|||
Protocol: notification.Protocol,
|
||||
IP: notification.IP,
|
||||
Role: notification.Role,
|
||||
Timestamp: notification.Timestamp,
|
||||
Timestamp: dateTime,
|
||||
Email: conn.User.Email,
|
||||
Object: nil,
|
||||
Metadata: metadata,
|
||||
|
@ -198,6 +200,7 @@ func newActionNotification(
|
|||
operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string,
|
||||
fileSize int64,
|
||||
openFlags, status int, elapsed int64,
|
||||
datetime time.Time,
|
||||
metadata map[string]string,
|
||||
) *notifier.FsEvent {
|
||||
var bucket, endpoint string
|
||||
|
@ -239,7 +242,7 @@ func newActionNotification(
|
|||
SessionID: sessionID,
|
||||
OpenFlags: openFlags,
|
||||
Role: user.Role,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: datetime.UnixNano(),
|
||||
Elapsed: elapsed,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
"github.com/rs/xid"
|
||||
|
@ -71,7 +72,7 @@ func TestNewActionNotification(t *testing.T) {
|
|||
c := NewBaseConnection("id", ProtocolSSH, "", "", user)
|
||||
sessionID := xid.New().String()
|
||||
a := newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(errors.New("fake error")), 0, nil)
|
||||
123, 0, c.getNotificationStatus(errors.New("fake error")), 0, time.Now(), nil)
|
||||
assert.Equal(t, user.Username, a.Username)
|
||||
assert.Equal(t, 0, len(a.Bucket))
|
||||
assert.Equal(t, 0, len(a.Endpoint))
|
||||
|
@ -79,38 +80,38 @@ func TestNewActionNotification(t *testing.T) {
|
|||
|
||||
user.FsConfig.Provider = sdk.S3FilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(nil), 0, nil)
|
||||
123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "s3bucket", a.Bucket)
|
||||
assert.Equal(t, "endpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
||||
user.FsConfig.Provider = sdk.GCSFilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(ErrQuotaExceeded), 0, nil)
|
||||
123, 0, c.getNotificationStatus(ErrQuotaExceeded), 0, time.Now(), nil)
|
||||
assert.Equal(t, "gcsbucket", a.Bucket)
|
||||
assert.Equal(t, 0, len(a.Endpoint))
|
||||
assert.Equal(t, 3, a.Status)
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded)), 0, nil)
|
||||
123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded)), 0, time.Now(), nil)
|
||||
assert.Equal(t, "gcsbucket", a.Bucket)
|
||||
assert.Equal(t, 0, len(a.Endpoint))
|
||||
assert.Equal(t, 3, a.Status)
|
||||
|
||||
user.FsConfig.Provider = sdk.HTTPFilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(nil), 0, nil)
|
||||
123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "httpendpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
||||
user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(nil), 0, nil)
|
||||
123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "azcontainer", a.Bucket)
|
||||
assert.Equal(t, "azendpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
|
||||
123, os.O_APPEND, c.getNotificationStatus(nil), 0, nil)
|
||||
123, os.O_APPEND, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "azcontainer", a.Bucket)
|
||||
assert.Equal(t, "azendpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
@ -118,7 +119,7 @@ func TestNewActionNotification(t *testing.T) {
|
|||
|
||||
user.FsConfig.Provider = sdk.SFTPFilesystemProvider
|
||||
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
|
||||
123, 0, c.getNotificationStatus(nil), 0, nil)
|
||||
123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
|
||||
assert.Equal(t, "sftpendpoint", a.Endpoint)
|
||||
}
|
||||
|
||||
|
@ -135,7 +136,7 @@ func TestActionHTTP(t *testing.T) {
|
|||
},
|
||||
}
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "",
|
||||
xid.New().String(), 123, 0, 1, 0, nil)
|
||||
xid.New().String(), 123, 0, 1, 0, time.Now(), nil)
|
||||
status, err := actionHandler.Handle(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, status)
|
||||
|
@ -175,7 +176,7 @@ func TestActionCMD(t *testing.T) {
|
|||
}
|
||||
sessionID := shortuuid.New()
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
|
||||
123, 0, 1, 0, map[string]string{"key": "value"})
|
||||
123, 0, 1, 0, time.Now(), map[string]string{"key": "value"})
|
||||
status, err := actionHandler.Handle(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, status)
|
||||
|
@ -208,7 +209,7 @@ func TestWrongActions(t *testing.T) {
|
|||
}
|
||||
|
||||
a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(),
|
||||
123, 0, 1, 0, nil)
|
||||
123, 0, 1, 0, time.Now(), nil)
|
||||
status, err := actionHandler.Handle(a)
|
||||
assert.Error(t, err, "action with bad command must fail")
|
||||
assert.Equal(t, 1, status)
|
||||
|
|
|
@ -110,7 +110,7 @@ func (d *dbDefender) AddEvent(ip, protocol string, event HostEvent) bool {
|
|||
eventManager.handleIPBlockedEvent(EventParams{
|
||||
Event: ipBlockedEventName,
|
||||
IP: ip,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -218,7 +218,7 @@ func (d *memoryDefender) AddEvent(ip, protocol string, event HostEvent) bool {
|
|||
eventManager.handleIPBlockedEvent(EventParams{
|
||||
Event: ipBlockedEventName,
|
||||
IP: ip,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
})
|
||||
} else {
|
||||
|
|
|
@ -58,6 +58,7 @@ const (
|
|||
maxAttachmentsSize = int64(10 * 1024 * 1024)
|
||||
objDataPlaceholder = "{{ObjectData}}"
|
||||
objDataPlaceholderString = "{{ObjectDataString}}"
|
||||
dateTimeMillisFormat = "2006-01-02T15:04:05.000"
|
||||
)
|
||||
|
||||
// Supported IDP login events
|
||||
|
@ -89,7 +90,7 @@ func init() {
|
|||
ObjectType: objectType,
|
||||
IP: ip,
|
||||
Role: role,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Object: object,
|
||||
}
|
||||
if u, ok := object.(*dataprovider.User); ok {
|
||||
|
@ -557,7 +558,7 @@ type EventParams struct {
|
|||
IP string
|
||||
Role string
|
||||
Email string
|
||||
Timestamp int64
|
||||
Timestamp time.Time
|
||||
UID string
|
||||
IDPCustomFields *map[string]string
|
||||
Object plugin.Renderer
|
||||
|
@ -641,7 +642,7 @@ func (p *EventParams) setBackupParams(backupPath string) {
|
|||
p.FsPath = backupPath
|
||||
p.ObjectName = filepath.Base(backupPath)
|
||||
p.VirtualPath = "/" + p.ObjectName
|
||||
p.Timestamp = time.Now().UnixNano()
|
||||
p.Timestamp = time.Now()
|
||||
info, err := os.Stat(backupPath)
|
||||
if err == nil {
|
||||
p.FileSize = info.Size()
|
||||
|
@ -775,6 +776,12 @@ func (*EventParams) getStringReplacement(val string, jsonEscaped bool) string {
|
|||
}
|
||||
|
||||
func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []string {
|
||||
var dateTimeString string
|
||||
if Config.TZ == "local" {
|
||||
dateTimeString = p.Timestamp.Local().Format(dateTimeMillisFormat)
|
||||
} else {
|
||||
dateTimeString = p.Timestamp.UTC().Format(dateTimeMillisFormat)
|
||||
}
|
||||
replacements := []string{
|
||||
"{{Name}}", p.getStringReplacement(p.Name, jsonEscaped),
|
||||
"{{Event}}", p.Event,
|
||||
|
@ -791,7 +798,8 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
|
|||
"{{IP}}", p.IP,
|
||||
"{{Role}}", p.getStringReplacement(p.Role, jsonEscaped),
|
||||
"{{Email}}", p.getStringReplacement(p.Email, jsonEscaped),
|
||||
"{{Timestamp}}", strconv.FormatInt(p.Timestamp, 10),
|
||||
"{{Timestamp}}", strconv.FormatInt(p.Timestamp.UnixNano(), 10),
|
||||
"{{DateTime}}", dateTimeString,
|
||||
"{{StatusString}}", p.getStatusString(),
|
||||
"{{UID}}", p.getStringReplacement(p.UID, jsonEscaped),
|
||||
"{{Ext}}", p.getStringReplacement(p.Extension, jsonEscaped),
|
||||
|
|
|
@ -800,6 +800,28 @@ func TestEventManagerErrors(t *testing.T) {
|
|||
stopEventScheduler()
|
||||
}
|
||||
|
||||
func TestDateTimePlaceholder(t *testing.T) {
|
||||
oldTZ := Config.TZ
|
||||
|
||||
Config.TZ = ""
|
||||
dateTime := time.Now()
|
||||
params := EventParams{
|
||||
Timestamp: dateTime,
|
||||
}
|
||||
replacements := params.getStringReplacements(false, false)
|
||||
r := strings.NewReplacer(replacements...)
|
||||
res := r.Replace("{{DateTime}}")
|
||||
assert.Equal(t, dateTime.UTC().Format(dateTimeMillisFormat), res)
|
||||
|
||||
Config.TZ = "local"
|
||||
replacements = params.getStringReplacements(false, false)
|
||||
r = strings.NewReplacer(replacements...)
|
||||
res = r.Replace("{{DateTime}}")
|
||||
assert.Equal(t, dateTime.Local().Format(dateTimeMillisFormat), res)
|
||||
|
||||
Config.TZ = oldTZ
|
||||
}
|
||||
|
||||
func TestEventRuleActions(t *testing.T) {
|
||||
actionName := "test rule action"
|
||||
action := dataprovider.BaseEventAction{
|
||||
|
|
|
@ -5431,7 +5431,7 @@ func TestBackupAsAttachment(t *testing.T) {
|
|||
|
||||
common.HandleCertificateEvent(common.EventParams{
|
||||
Name: "example.com",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
Event: renewalEvent,
|
||||
})
|
||||
|
@ -7108,7 +7108,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
Recipients: []string{"test@example.com"},
|
||||
Subject: `"{{Event}} {{StatusString}}"`,
|
||||
ContentType: 0,
|
||||
Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}}",
|
||||
Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}} Date time: {{DateTime}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -7163,7 +7163,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
|
||||
common.HandleCertificateEvent(common.EventParams{
|
||||
Name: "example.com",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
Event: renewalEvent,
|
||||
})
|
||||
|
@ -7178,9 +7178,10 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
dateTime := time.Now()
|
||||
params := common.EventParams{
|
||||
Name: "example.com",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: dateTime,
|
||||
Status: 2,
|
||||
Event: renewalEvent,
|
||||
}
|
||||
|
@ -7195,6 +7196,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
|
||||
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
||||
assert.Contains(t, email.Data, dateTime.UTC().Format("2006-01-02T15:04:05.000"))
|
||||
assert.Contains(t, email.Data, errRenew.Error())
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
||||
|
@ -7208,7 +7210,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
|||
// ignored no more certificate rules
|
||||
common.HandleCertificateEvent(common.EventParams{
|
||||
Name: "example.com",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
Event: renewalEvent,
|
||||
})
|
||||
|
|
|
@ -408,7 +408,7 @@ func (t *oidcToken) getUser(r *http.Request) error {
|
|||
Name: t.Username,
|
||||
IP: ipAddr,
|
||||
Protocol: common.ProtocolOIDC,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Timestamp: time.Now(),
|
||||
Status: 1,
|
||||
}
|
||||
if t.isAdmin() {
|
||||
|
|
|
@ -1057,6 +1057,7 @@
|
|||
"ip": "Client IP address",
|
||||
"role": "User or admin role",
|
||||
"timestamp": "Event timestamp as nanoseconds since epoch",
|
||||
"datetime": "Event timestamp formatted as YYYY-MM-DDTHH:MM:SS.ZZZ",
|
||||
"email": "For filesystem events, this is the email associated with the user performing the action. For the provider events, this is the email associated with the affected user or admin. Blank in all other cases",
|
||||
"object_data": "Provider object data serialized as JSON with sensitive fields removed",
|
||||
"object_data_string": "Provider object data as JSON escaped string with sensitive fields removed",
|
||||
|
|
|
@ -1057,6 +1057,7 @@
|
|||
"ip": "Indirizzo IP del client",
|
||||
"role": "Ruolo dell'utente o dell'amministratore",
|
||||
"timestamp": "Timestamp dell'evento in nanosecondi dall'epoch time",
|
||||
"datetime": "Timestamp dell'evento formattato come YYYY-MM-DDTHH:MM:SS.ZZZ",
|
||||
"email": "Per gli eventi del file system, questa è l'e-mail associata all'utente che esegue l'azione. Per gli eventi del provider, si tratta dell'e-mail associata all'utente o all'amministratore interessato. Vuoto in tutti gli altri casi",
|
||||
"object_data": "Dati dell'oggetto provider serializzati come JSON con campi sensibili rimossi",
|
||||
"object_data_string": "Dati dell'oggetto provider serializzati come stringa JSON escaped con campi sensibili rimossi",
|
||||
|
|
|
@ -941,6 +941,9 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
<p>
|
||||
<span class="shortcut">{{`{{Timestamp}}`}}</span> => <span data-i18n="actions.placeholders_modal.timestamp">Event timestamp as nanoseconds since epoch.</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="shortcut">{{`{{DateTime}}`}}</span> => <span data-i18n="actions.placeholders_modal.datetime">Timestamp formatted as YYYY-MM-DDTHH:MM:SS.ZZZ.</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="shortcut">{{`{{Email}}`}}</span> => <span data-i18n="actions.placeholders_modal.email">For filesystem events, this is the email associated with the user performing the action. For the provider events, this is the email associated with the affected user or admin. Blank in all other cases.</span>
|
||||
</p>
|
||||
|
|
Loading…
Reference in a new issue