EventManager: add datetime placeholder

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2024-10-08 18:39:00 +02:00
parent 0c470b9202
commit 4103344989
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
12 changed files with 70 additions and 29 deletions

View file

@ -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

View file

@ -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,
}

View file

@ -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)

View file

@ -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,
})
}

View file

@ -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 {

View file

@ -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),

View file

@ -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{

View file

@ -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,
})

View file

@ -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() {

View file

@ -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",

View file

@ -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",

View file

@ -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>