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{ params := common.EventParams{
Name: domain, Name: domain,
Event: "Certificate renewal", Event: "Certificate renewal",
Timestamp: time.Now().UnixNano(), Timestamp: time.Now(),
} }
if err != nil { if err != nil {
params.Status = 2 params.Status = 2

View file

@ -92,8 +92,9 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
if !hasHook && !hasNotifiersPlugin && !hasRules { if !hasHook && !hasNotifiersPlugin && !hasRules {
return 0, nil return 0, nil
} }
dateTime := time.Now()
event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "", 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 { if hasNotifiersPlugin {
plugin.Handler.NotifyFsEvent(event) plugin.Handler.NotifyFsEvent(event)
} }
@ -113,7 +114,7 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
Protocol: event.Protocol, Protocol: event.Protocol,
IP: event.IP, IP: event.IP,
Role: event.Role, Role: event.Role,
Timestamp: event.Timestamp, Timestamp: dateTime,
Email: conn.User.Email, Email: conn.User.Email,
Object: nil, Object: nil,
} }
@ -138,8 +139,9 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
if !hasHook && !hasNotifiersPlugin && !hasRules { if !hasHook && !hasNotifiersPlugin && !hasRules {
return nil return nil
} }
dateTime := time.Now()
notification := newActionNotification(&conn.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd, 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 { if hasNotifiersPlugin {
plugin.Handler.NotifyFsEvent(notification) plugin.Handler.NotifyFsEvent(notification)
} }
@ -160,7 +162,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
Protocol: notification.Protocol, Protocol: notification.Protocol,
IP: notification.IP, IP: notification.IP,
Role: notification.Role, Role: notification.Role,
Timestamp: notification.Timestamp, Timestamp: dateTime,
Email: conn.User.Email, Email: conn.User.Email,
Object: nil, Object: nil,
Metadata: metadata, Metadata: metadata,
@ -198,6 +200,7 @@ func newActionNotification(
operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string, operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string,
fileSize int64, fileSize int64,
openFlags, status int, elapsed int64, openFlags, status int, elapsed int64,
datetime time.Time,
metadata map[string]string, metadata map[string]string,
) *notifier.FsEvent { ) *notifier.FsEvent {
var bucket, endpoint string var bucket, endpoint string
@ -239,7 +242,7 @@ func newActionNotification(
SessionID: sessionID, SessionID: sessionID,
OpenFlags: openFlags, OpenFlags: openFlags,
Role: user.Role, Role: user.Role,
Timestamp: time.Now().UnixNano(), Timestamp: datetime.UnixNano(),
Elapsed: elapsed, Elapsed: elapsed,
Metadata: metadata, Metadata: metadata,
} }

View file

@ -22,6 +22,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"testing" "testing"
"time"
"github.com/lithammer/shortuuid/v4" "github.com/lithammer/shortuuid/v4"
"github.com/rs/xid" "github.com/rs/xid"
@ -71,7 +72,7 @@ func TestNewActionNotification(t *testing.T) {
c := NewBaseConnection("id", ProtocolSSH, "", "", user) c := NewBaseConnection("id", ProtocolSSH, "", "", user)
sessionID := xid.New().String() sessionID := xid.New().String()
a := newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID, 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, user.Username, a.Username)
assert.Equal(t, 0, len(a.Bucket)) assert.Equal(t, 0, len(a.Bucket))
assert.Equal(t, 0, len(a.Endpoint)) assert.Equal(t, 0, len(a.Endpoint))
@ -79,38 +80,38 @@ func TestNewActionNotification(t *testing.T) {
user.FsConfig.Provider = sdk.S3FilesystemProvider user.FsConfig.Provider = sdk.S3FilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID, 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, "s3bucket", a.Bucket)
assert.Equal(t, "endpoint", a.Endpoint) assert.Equal(t, "endpoint", a.Endpoint)
assert.Equal(t, 1, a.Status) assert.Equal(t, 1, a.Status)
user.FsConfig.Provider = sdk.GCSFilesystemProvider user.FsConfig.Provider = sdk.GCSFilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, 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, "gcsbucket", a.Bucket)
assert.Equal(t, 0, len(a.Endpoint)) assert.Equal(t, 0, len(a.Endpoint))
assert.Equal(t, 3, a.Status) assert.Equal(t, 3, a.Status)
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, 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, "gcsbucket", a.Bucket)
assert.Equal(t, 0, len(a.Endpoint)) assert.Equal(t, 0, len(a.Endpoint))
assert.Equal(t, 3, a.Status) assert.Equal(t, 3, a.Status)
user.FsConfig.Provider = sdk.HTTPFilesystemProvider user.FsConfig.Provider = sdk.HTTPFilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID, 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, "httpendpoint", a.Endpoint)
assert.Equal(t, 1, a.Status) assert.Equal(t, 1, a.Status)
user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, 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, "azcontainer", a.Bucket)
assert.Equal(t, "azendpoint", a.Endpoint) assert.Equal(t, "azendpoint", a.Endpoint)
assert.Equal(t, 1, a.Status) assert.Equal(t, 1, a.Status)
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, 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, "azcontainer", a.Bucket)
assert.Equal(t, "azendpoint", a.Endpoint) assert.Equal(t, "azendpoint", a.Endpoint)
assert.Equal(t, 1, a.Status) assert.Equal(t, 1, a.Status)
@ -118,7 +119,7 @@ func TestNewActionNotification(t *testing.T) {
user.FsConfig.Provider = sdk.SFTPFilesystemProvider user.FsConfig.Provider = sdk.SFTPFilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID, 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) assert.Equal(t, "sftpendpoint", a.Endpoint)
} }
@ -135,7 +136,7 @@ func TestActionHTTP(t *testing.T) {
}, },
} }
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", 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) status, err := actionHandler.Handle(a)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, status) assert.Equal(t, 1, status)
@ -175,7 +176,7 @@ func TestActionCMD(t *testing.T) {
} }
sessionID := shortuuid.New() sessionID := shortuuid.New()
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID, 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) status, err := actionHandler.Handle(a)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, status) assert.Equal(t, 1, status)
@ -208,7 +209,7 @@ func TestWrongActions(t *testing.T) {
} }
a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(), 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) status, err := actionHandler.Handle(a)
assert.Error(t, err, "action with bad command must fail") assert.Error(t, err, "action with bad command must fail")
assert.Equal(t, 1, status) assert.Equal(t, 1, status)

View file

@ -110,7 +110,7 @@ func (d *dbDefender) AddEvent(ip, protocol string, event HostEvent) bool {
eventManager.handleIPBlockedEvent(EventParams{ eventManager.handleIPBlockedEvent(EventParams{
Event: ipBlockedEventName, Event: ipBlockedEventName,
IP: ip, IP: ip,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now(),
Status: 1, Status: 1,
}) })
} }

View file

@ -218,7 +218,7 @@ func (d *memoryDefender) AddEvent(ip, protocol string, event HostEvent) bool {
eventManager.handleIPBlockedEvent(EventParams{ eventManager.handleIPBlockedEvent(EventParams{
Event: ipBlockedEventName, Event: ipBlockedEventName,
IP: ip, IP: ip,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now(),
Status: 1, Status: 1,
}) })
} else { } else {

View file

@ -58,6 +58,7 @@ const (
maxAttachmentsSize = int64(10 * 1024 * 1024) maxAttachmentsSize = int64(10 * 1024 * 1024)
objDataPlaceholder = "{{ObjectData}}" objDataPlaceholder = "{{ObjectData}}"
objDataPlaceholderString = "{{ObjectDataString}}" objDataPlaceholderString = "{{ObjectDataString}}"
dateTimeMillisFormat = "2006-01-02T15:04:05.000"
) )
// Supported IDP login events // Supported IDP login events
@ -89,7 +90,7 @@ func init() {
ObjectType: objectType, ObjectType: objectType,
IP: ip, IP: ip,
Role: role, Role: role,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now(),
Object: object, Object: object,
} }
if u, ok := object.(*dataprovider.User); ok { if u, ok := object.(*dataprovider.User); ok {
@ -557,7 +558,7 @@ type EventParams struct {
IP string IP string
Role string Role string
Email string Email string
Timestamp int64 Timestamp time.Time
UID string UID string
IDPCustomFields *map[string]string IDPCustomFields *map[string]string
Object plugin.Renderer Object plugin.Renderer
@ -641,7 +642,7 @@ func (p *EventParams) setBackupParams(backupPath string) {
p.FsPath = backupPath p.FsPath = backupPath
p.ObjectName = filepath.Base(backupPath) p.ObjectName = filepath.Base(backupPath)
p.VirtualPath = "/" + p.ObjectName p.VirtualPath = "/" + p.ObjectName
p.Timestamp = time.Now().UnixNano() p.Timestamp = time.Now()
info, err := os.Stat(backupPath) info, err := os.Stat(backupPath)
if err == nil { if err == nil {
p.FileSize = info.Size() p.FileSize = info.Size()
@ -775,6 +776,12 @@ func (*EventParams) getStringReplacement(val string, jsonEscaped bool) string {
} }
func (p *EventParams) getStringReplacements(addObjectData, 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{ replacements := []string{
"{{Name}}", p.getStringReplacement(p.Name, jsonEscaped), "{{Name}}", p.getStringReplacement(p.Name, jsonEscaped),
"{{Event}}", p.Event, "{{Event}}", p.Event,
@ -791,7 +798,8 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
"{{IP}}", p.IP, "{{IP}}", p.IP,
"{{Role}}", p.getStringReplacement(p.Role, jsonEscaped), "{{Role}}", p.getStringReplacement(p.Role, jsonEscaped),
"{{Email}}", p.getStringReplacement(p.Email, 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(), "{{StatusString}}", p.getStatusString(),
"{{UID}}", p.getStringReplacement(p.UID, jsonEscaped), "{{UID}}", p.getStringReplacement(p.UID, jsonEscaped),
"{{Ext}}", p.getStringReplacement(p.Extension, jsonEscaped), "{{Ext}}", p.getStringReplacement(p.Extension, jsonEscaped),

View file

@ -800,6 +800,28 @@ func TestEventManagerErrors(t *testing.T) {
stopEventScheduler() 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) { func TestEventRuleActions(t *testing.T) {
actionName := "test rule action" actionName := "test rule action"
action := dataprovider.BaseEventAction{ action := dataprovider.BaseEventAction{

View file

@ -5431,7 +5431,7 @@ func TestBackupAsAttachment(t *testing.T) {
common.HandleCertificateEvent(common.EventParams{ common.HandleCertificateEvent(common.EventParams{
Name: "example.com", Name: "example.com",
Timestamp: time.Now().UnixNano(), Timestamp: time.Now(),
Status: 1, Status: 1,
Event: renewalEvent, Event: renewalEvent,
}) })
@ -7108,7 +7108,7 @@ func TestEventRuleCertificate(t *testing.T) {
Recipients: []string{"test@example.com"}, Recipients: []string{"test@example.com"},
Subject: `"{{Event}} {{StatusString}}"`, Subject: `"{{Event}} {{StatusString}}"`,
ContentType: 0, 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{ common.HandleCertificateEvent(common.EventParams{
Name: "example.com", Name: "example.com",
Timestamp: time.Now().UnixNano(), Timestamp: time.Now(),
Status: 1, Status: 1,
Event: renewalEvent, Event: renewalEvent,
}) })
@ -7178,9 +7178,10 @@ func TestEventRuleCertificate(t *testing.T) {
assert.Contains(t, email.Data, `Domain: example.com Timestamp`) assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
lastReceivedEmail.reset() lastReceivedEmail.reset()
dateTime := time.Now()
params := common.EventParams{ params := common.EventParams{
Name: "example.com", Name: "example.com",
Timestamp: time.Now().UnixNano(), Timestamp: dateTime,
Status: 2, Status: 2,
Event: renewalEvent, Event: renewalEvent,
} }
@ -7195,6 +7196,7 @@ func TestEventRuleCertificate(t *testing.T) {
assert.True(t, slices.Contains(email.To, "test@example.com")) 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, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
assert.Contains(t, email.Data, `Domain: example.com Timestamp`) 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()) assert.Contains(t, email.Data, errRenew.Error())
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK) _, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
@ -7208,7 +7210,7 @@ func TestEventRuleCertificate(t *testing.T) {
// ignored no more certificate rules // ignored no more certificate rules
common.HandleCertificateEvent(common.EventParams{ common.HandleCertificateEvent(common.EventParams{
Name: "example.com", Name: "example.com",
Timestamp: time.Now().UnixNano(), Timestamp: time.Now(),
Status: 1, Status: 1,
Event: renewalEvent, Event: renewalEvent,
}) })

View file

@ -408,7 +408,7 @@ func (t *oidcToken) getUser(r *http.Request) error {
Name: t.Username, Name: t.Username,
IP: ipAddr, IP: ipAddr,
Protocol: common.ProtocolOIDC, Protocol: common.ProtocolOIDC,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now(),
Status: 1, Status: 1,
} }
if t.isAdmin() { if t.isAdmin() {

View file

@ -1057,6 +1057,7 @@
"ip": "Client IP address", "ip": "Client IP address",
"role": "User or admin role", "role": "User or admin role",
"timestamp": "Event timestamp as nanoseconds since epoch", "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", "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": "Provider object data serialized as JSON with sensitive fields removed",
"object_data_string": "Provider object data as JSON escaped string 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", "ip": "Indirizzo IP del client",
"role": "Ruolo dell'utente o dell'amministratore", "role": "Ruolo dell'utente o dell'amministratore",
"timestamp": "Timestamp dell'evento in nanosecondi dall'epoch time", "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", "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": "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", "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> <p>
<span class="shortcut">{{`{{Timestamp}}`}}</span> => <span data-i18n="actions.placeholders_modal.timestamp">Event timestamp as nanoseconds since epoch.</span> <span class="shortcut">{{`{{Timestamp}}`}}</span> => <span data-i18n="actions.placeholders_modal.timestamp">Event timestamp as nanoseconds since epoch.</span>
</p> </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> <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> <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> </p>