Skip to content

Commit

Permalink
EventManager: add datetime placeholder
Browse files Browse the repository at this point in the history
Signed-off-by: Nicola Murino <[email protected]>
  • Loading branch information
drakkan committed Oct 8, 2024
1 parent 0c470b9 commit 4103344
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 29 deletions.
2 changes: 1 addition & 1 deletion internal/acme/acme.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 8 additions & 5 deletions internal/common/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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,
}
Expand All @@ -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)
}
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -239,7 +242,7 @@ func newActionNotification(
SessionID: sessionID,
OpenFlags: openFlags,
Role: user.Role,
Timestamp: time.Now().UnixNano(),
Timestamp: datetime.UnixNano(),
Elapsed: elapsed,
Metadata: metadata,
}
Expand Down
23 changes: 12 additions & 11 deletions internal/common/actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"path/filepath"
"runtime"
"testing"
"time"

"github.com/lithammer/shortuuid/v4"
"github.com/rs/xid"
Expand Down Expand Up @@ -71,54 +72,54 @@ 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))
assert.Equal(t, 2, a.Status)

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)
assert.Equal(t, os.O_APPEND, a.OpenFlags)

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

Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion internal/common/defenderdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
Expand Down
2 changes: 1 addition & 1 deletion internal/common/defendermem.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 12 additions & 4 deletions internal/common/eventmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand All @@ -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),
Expand Down
22 changes: 22 additions & 0 deletions internal/common/eventmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
12 changes: 7 additions & 5 deletions internal/common/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down Expand Up @@ -7108,7 +7108,7 @@ func TestEventRuleCertificate(t *testing.T) {
Recipients: []string{"[email protected]"},
Subject: `"{{Event}} {{StatusString}}"`,
ContentType: 0,
Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}}",
Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}} Date time: {{DateTime}}",
},
},
}
Expand Down Expand Up @@ -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,
})
Expand All @@ -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,
}
Expand All @@ -7195,6 +7196,7 @@ func TestEventRuleCertificate(t *testing.T) {
assert.True(t, slices.Contains(email.To, "[email protected]"))
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)
Expand All @@ -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,
})
Expand Down
2 changes: 1 addition & 1 deletion internal/httpd/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions static/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions static/locales/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions templates/webadmin/eventaction.html
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,9 @@ <h3 data-i18n="actions.placeholders_modal_title" class="modal-title">Supported p
<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>
Expand Down

0 comments on commit 4103344

Please sign in to comment.