diff --git a/models/audit/action.go b/models/audit/action.go index d92130f158df5..dba43edb6d4d2 100644 --- a/models/audit/action.go +++ b/models/audit/action.go @@ -123,3 +123,240 @@ const ( SystemOAuth2ApplicationSecret Action = "system:oauth2application:secret" SystemOAuth2ApplicationRemove Action = "system:oauth2application:remove" ) + +func (a Action) LocaleKey() string { + switch a { + case UserImpersonation: + return "audit.action.user.impersonation" + case UserCreate: + return "audit.action.user.create" + case UserDelete: + return "audit.action.user.delete" + case UserAuthenticationFailTwoFactor: + return "audit.action.user.authentication.fail.twofactor" + case UserAuthenticationSource: + return "audit.action.user.authentication.source" + case UserActive: + return "audit.action.user.active" + case UserRestricted: + return "audit.action.user.restricted" + case UserAdmin: + return "audit.action.user.admin" + case UserName: + return "audit.action.user.name" + case UserPassword: + return "audit.action.user.password" + case UserPasswordResetRequest: + return "audit.action.user.password.resetrequest" + case UserVisibility: + return "audit.action.user.visibility" + case UserEmailPrimaryChange: + return "audit.action.user.email.primary" + case UserEmailAdd: + return "audit.action.user.email.add" + case UserEmailActivate: + return "audit.action.user.email.activate" + case UserEmailRemove: + return "audit.action.user.email.remove" + case UserTwoFactorEnable: + return "audit.action.user.twofactor.enable" + case UserTwoFactorRegenerate: + return "audit.action.user.twofactor.regenerate" + case UserTwoFactorDisable: + return "audit.action.user.twofactor.disable" + case UserWebAuthAdd: + return "audit.action.user.webauth.add" + case UserWebAuthRemove: + return "audit.action.user.webauth.remove" + case UserExternalLoginAdd: + return "audit.action.user.externallogin.add" + case UserExternalLoginRemove: + return "audit.action.user.externallogin.remove" + case UserOpenIDAdd: + return "audit.action.user.openid.add" + case UserOpenIDRemove: + return "audit.action.user.openid.remove" + case UserAccessTokenAdd: + return "audit.action.user.accesstoken.add" + case UserAccessTokenRemove: + return "audit.action.user.accesstoken.remove" + case UserOAuth2ApplicationAdd: + return "audit.action.user.oauth2application.add" + case UserOAuth2ApplicationUpdate: + return "audit.action.user.oauth2application.update" + case UserOAuth2ApplicationSecret: + return "audit.action.user.oauth2application.secret" + case UserOAuth2ApplicationGrant: + return "audit.action.user.oauth2application.grant" + case UserOAuth2ApplicationRevoke: + return "audit.action.user.oauth2application.revoke" + case UserOAuth2ApplicationRemove: + return "audit.action.user.oauth2application.remove" + case UserKeySSHAdd: + return "audit.action.user.key.ssh.add" + case UserKeySSHRemove: + return "audit.action.user.key.ssh.remove" + case UserKeyPrincipalAdd: + return "audit.action.user.key.principal.add" + case UserKeyPrincipalRemove: + return "audit.action.user.key.principal.remove" + case UserKeyGPGAdd: + return "audit.action.user.key.gpg.add" + case UserKeyGPGRemove: + return "audit.action.user.key.gpg.remove" + case UserSecretAdd: + return "audit.action.user.secret.add" + case UserSecretUpdate: + return "audit.action.user.secret.update" + case UserSecretRemove: + return "audit.action.user.secret.remove" + case UserWebhookAdd: + return "audit.action.user.webhook.add" + case UserWebhookUpdate: + return "audit.action.user.webhook.update" + case UserWebhookRemove: + return "audit.action.user.webhook.remove" + + case OrganizationCreate: + return "audit.action.organization.create" + case OrganizationDelete: + return "audit.action.organization.delete" + case OrganizationName: + return "audit.action.organization.name" + case OrganizationVisibility: + return "audit.action.organization.visibility" + case OrganizationTeamAdd: + return "audit.action.organization.team.add" + case OrganizationTeamUpdate: + return "audit.action.organization.team.update" + case OrganizationTeamRemove: + return "audit.action.organization.team.remove" + case OrganizationTeamPermission: + return "audit.action.organization.team.permission" + case OrganizationTeamMemberAdd: + return "audit.action.organization.team.member.add" + case OrganizationTeamMemberRemove: + return "audit.action.organization.team.member.remove" + case OrganizationOAuth2ApplicationAdd: + return "audit.action.organization.oauth2application.add" + case OrganizationOAuth2ApplicationUpdate: + return "audit.action.organization.oauth2application.update" + case OrganizationOAuth2ApplicationSecret: + return "audit.action.organization.oauth2application.secret" + case OrganizationOAuth2ApplicationRemove: + return "audit.action.organization.oauth2application.remove" + case OrganizationSecretAdd: + return "audit.action.organization.secret.add" + case OrganizationSecretUpdate: + return "audit.action.organization.secret.update" + case OrganizationSecretRemove: + return "audit.action.organization.secret.remove" + case OrganizationWebhookAdd: + return "audit.action.organization.webhook.add" + case OrganizationWebhookUpdate: + return "audit.action.organization.webhook.update" + case OrganizationWebhookRemove: + return "audit.action.organization.webhook.remove" + + case RepositoryCreate: + return "audit.action.repository.create" + case RepositoryCreateFork: + return "audit.action.repository.create.fork" + case RepositoryArchive: + return "audit.action.repository.archive" + case RepositoryUnarchive: + return "audit.action.repository.unarchive" + case RepositoryDelete: + return "audit.action.repository.delete" + case RepositoryName: + return "audit.action.repository.name" + case RepositoryVisibility: + return "audit.action.repository.visibility" + case RepositoryConvertFork: + return "audit.action.repository.convert.fork" + case RepositoryConvertMirror: + return "audit.action.repository.convert.mirror" + case RepositoryMirrorPushAdd: + return "audit.action.repository.mirror.push.add" + case RepositoryMirrorPushRemove: + return "audit.action.repository.mirror.push.remove" + case RepositorySigningVerification: + return "audit.action.repository.signingverification" + case RepositoryTransferStart: + return "audit.action.repository.transfer.start" + case RepositoryTransferFinish: + return "audit.action.repository.transfer.finish" + case RepositoryTransferCancel: + return "audit.action.repository.transfer.cancel" + case RepositoryWikiDelete: + return "audit.action.repository.wiki.delete" + case RepositoryCollaboratorAdd: + return "audit.action.repository.collaborator.add" + case RepositoryCollaboratorAccess: + return "audit.action.repository.collaborator.access" + case RepositoryCollaboratorRemove: + return "audit.action.repository.collaborator.remove" + case RepositoryCollaboratorTeamAdd: + return "audit.action.repository.collaborator.team.add" + case RepositoryCollaboratorTeamRemove: + return "audit.action.repository.collaborator.team.remove" + case RepositoryBranchDefault: + return "audit.action.repository.branch.default" + case RepositoryBranchProtectionAdd: + return "audit.action.repository.branch.protection.add" + case RepositoryBranchProtectionUpdate: + return "audit.action.repository.branch.protection.update" + case RepositoryBranchProtectionRemove: + return "audit.action.repository.branch.protection.remove" + case RepositoryTagProtectionAdd: + return "audit.action.repository.tag.protection.add" + case RepositoryTagProtectionUpdate: + return "audit.action.repository.tag.protection.update" + case RepositoryTagProtectionRemove: + return "audit.action.repository.tag.protection.remove" + case RepositoryWebhookAdd: + return "audit.action.repository.webhook.add" + case RepositoryWebhookUpdate: + return "audit.action.repository.webhook.update" + case RepositoryWebhookRemove: + return "audit.action.repository.webhook.remove" + case RepositoryDeployKeyAdd: + return "audit.action.repository.deploykey.add" + case RepositoryDeployKeyRemove: + return "audit.action.repository.deploykey.remove" + case RepositorySecretAdd: + return "audit.action.repository.secret.add" + case RepositorySecretUpdate: + return "audit.action.repository.secret.update" + case RepositorySecretRemove: + return "audit.action.repository.secret.remove" + + case SystemStartup: + return "audit.action.system.startup" + case SystemShutdown: + return "audit.action.system.shutdown" + case SystemWebhookAdd: + return "audit.action.system.webhook.add" + case SystemWebhookUpdate: + return "audit.action.system.webhook.update" + case SystemWebhookRemove: + return "audit.action.system.webhook.remove" + case SystemAuthenticationSourceAdd: + return "audit.action.system.authenticationsource.add" + case SystemAuthenticationSourceUpdate: + return "audit.action.system.authenticationsource.update" + case SystemAuthenticationSourceRemove: + return "audit.action.system.authenticationsource.remove" + case SystemOAuth2ApplicationAdd: + return "audit.action.system.oauth2application.add" + case SystemOAuth2ApplicationUpdate: + return "audit.action.system.oauth2application.update" + case SystemOAuth2ApplicationSecret: + return "audit.action.system.oauth2application.secret" + case SystemOAuth2ApplicationRemove: + return "audit.action.system.oauth2application.remove" + + default: + panic("unknown audit action: " + a) + } +} diff --git a/models/audit/audit_event.go b/models/audit/audit_event.go index 8e95a051589a1..c8b9945bc4034 100644 --- a/models/audit/audit_event.go +++ b/models/audit/audit_event.go @@ -17,16 +17,16 @@ func init() { } type Event struct { - ID int64 `xorm:"pk autoincr"` - Action Action `xorm:"INDEX NOT NULL"` - ActorID int64 `xorm:"INDEX NOT NULL"` - ScopeType ObjectType `xorm:"INDEX(scope) NOT NULL"` - ScopeID int64 `xorm:"INDEX(scope) NOT NULL"` - TargetType ObjectType `xorm:"NOT NULL"` - TargetID int64 `xorm:"NOT NULL"` - Message string - IPAddress string - TimestampUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL"` + ID int64 `xorm:"pk autoincr"` + Action Action `xorm:"INDEX NOT NULL"` + ActorID int64 `xorm:"INDEX NOT NULL"` + ScopeType ObjectType `xorm:"INDEX(scope) NOT NULL"` + ScopeID int64 `xorm:"INDEX(scope) NOT NULL"` + TargetType ObjectType `xorm:"NOT NULL"` + TargetID int64 `xorm:"NOT NULL"` + MessageContext []any `xorm:"JSON TEXT"` + IPAddress string + TimestampUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL"` } func (*Event) TableName() string { diff --git a/models/migrations/v1_22/v288.go b/models/migrations/v1_22/v288.go index 68aeb4af861fb..b4d68e694605e 100644 --- a/models/migrations/v1_22/v288.go +++ b/models/migrations/v1_22/v288.go @@ -11,16 +11,16 @@ import ( func AddAuditEventTable(x *xorm.Engine) error { type AuditEvent struct { - ID int64 `xorm:"pk autoincr"` - Action string `xorm:"INDEX NOT NULL"` - ActorID int64 `xorm:"INDEX NOT NULL"` - ScopeType string `xorm:"INDEX(scope) NOT NULL"` - ScopeID int64 `xorm:"INDEX(scope) NOT NULL"` - TargetType string `xorm:"NOT NULL"` - TargetID int64 `xorm:"NOT NULL"` - Message string - IPAddress string - TimestampUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL"` + ID int64 `xorm:"pk autoincr"` + Action string `xorm:"INDEX NOT NULL"` + ActorID int64 `xorm:"INDEX NOT NULL"` + ScopeType string `xorm:"INDEX(scope) NOT NULL"` + ScopeID int64 `xorm:"INDEX(scope) NOT NULL"` + TargetType string `xorm:"NOT NULL"` + TargetID int64 `xorm:"NOT NULL"` + MessageContext []any `xorm:"JSON TEXT"` + IPAddress string + TimestampUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL"` } return x.Sync(&AuditEvent{}) diff --git a/modules/translation/mock.go b/modules/translation/mock.go index 1f0559f38d0d2..15d1d3f6bf582 100644 --- a/modules/translation/mock.go +++ b/modules/translation/mock.go @@ -25,6 +25,10 @@ func (l MockLocale) Tr(s string, a ...any) template.HTML { return template.HTML(s) } +func (l MockLocale) TrExpand(s string, a []any) template.HTML { + return l.Tr(s, a...) +} + func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML { return template.HTML(key1) } diff --git a/modules/translation/translation.go b/modules/translation/translation.go index b7c18f610adae..1d3b761d0f428 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -31,6 +31,7 @@ type Locale interface { TrString(string, ...any) string Tr(key string, args ...any) template.HTML + TrExpand(key string, args []any) template.HTML TrN(cnt any, key1, keyN string, args ...any) template.HTML PrettyNumber(v any) string @@ -226,6 +227,10 @@ func (l *locale) Tr(s string, args ...any) template.HTML { return l.TrHTML(s, args...) } +func (l *locale) TrExpand(s string, args []any) template.HTML { + return l.Tr(s, args...) +} + // TrN returns translated message for plural text translation func (l *locale) TrN(cnt any, key1, keyN string, args ...any) template.HTML { var c int64 diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 99fea03d5bf8d..e7cc8bf0a6895 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3637,3 +3637,121 @@ normal_file = Normal file executable_file = Executable file symbolic_link = Symbolic link submodule = Submodule + +[audit] +action.user.impersonation = User %s impersonating user %s. +action.user.create = Created user %s. +action.user.delete = Deleted user %s. +action.user.authentication.fail.twofactor = Failed two-factor authentication for user %s. +action.user.authentication.source = Changed authentication source of user %s. +action.user.active = Changed activation status of user %s to %s. +action.user.restricted = Changed restricted status of user %s to %s. +action.user.admin = Changed admin status of user %s to %s. +action.user.name = Changed user name to %s. +action.user.password = Changed password of user %s. +action.user.password.resetrequest = Requested password reset for user %s. +action.user.visibility = Changed visibility of user %s to %s. +action.user.email.primary = Changed primary email of user %s to %s. +action.user.email.add = Added email %s to user %s. +action.user.email.activate = Changed activation status of email %s of user %s to %s. +action.user.email.remove = Removed email %s from user %s. +action.user.twofactor.enable = Enabled two-factor authentication for user %s. +action.user.twofactor.regenerate = Regenerated two-factor authentication secret for user %s. +action.user.twofactor.disable = Disabled two-factor authentication for user %s. +action.user.webauth.add = Added WebAuthn key %s for user %s. +action.user.webauth.remove = Removed WebAuthn key %s from user %s. +action.user.externallogin.add = Added external login %s for user %s using provider %s. +action.user.externallogin.remove = Removed external login %s for user %s from provider. +action.user.openid.add = Associated OpenID %s to user %s. +action.user.openid.remove = Removed OpenID %s from user %s. +action.user.accesstoken.add = Added access token %s for user %s with scope %s. +action.user.accesstoken.remove = Removed access token %s from user %s. +action.user.oauth2application.add = Created OAuth2 application %s for user %s. +action.user.oauth2application.update = Updated OAuth2 application %s of user %s. +action.user.oauth2application.secret = Regenerated secret for OAuth2 application %s of user %s. +action.user.oauth2application.grant = Granted OAuth2 access to application %s of user %s. +action.user.oauth2application.revoke = Revoked OAuth2 grant for application %s of user %s. +action.user.oauth2application.remove = Removed OAuth2 application %s of user %s. +action.user.key.ssh.add = Added SSH key %s for user %s. +action.user.key.ssh.remove = Removed SSH key %s of user %s. +action.user.key.principal.add = Added principal key %s for user %s. +action.user.key.principal.remove = Removed principal key %s of user %s. +action.user.key.gpg.add = Added GPG key %s for user %s. +action.user.key.gpg.remove = Removed GPG key %s of user %s. +action.user.secret.add = Added secret %s for user %s. +action.user.secret.update = Updated secret %s of user %s. +action.user.secret.remove = Removed secret %s of user %s. +action.user.webhook.add = Added webhook %s for user %s. +action.user.webhook.update = Updated webhook %s of user %s. +action.user.webhook.remove = Removed webhook %s of user %s. + +action.organization.create = Created organization %s. +action.organization.delete = Deleted organization %s. +action.organization.name = Changed organization name to %s. +action.organization.visibility = Changed visibility of organization %s to %s. +action.organization.team.add = Added team %s to organization %s. +action.organization.team.update = Updated settings of team %s/%s. +action.organization.team.remove = Removed team %s from organization %s. +action.organization.team.permission = Changed permission of team %s/%s to %s. +action.organization.team.member.add = Added user %s to team %s/%s. +action.organization.team.member.remove = Removed user %s from team %s/%s. +action.organization.oauth2application.add = Created OAuth2 application %s for organization %s. +action.organization.oauth2application.update = Updated OAuth2 application %s of organization %s. +action.organization.oauth2application.secret = Regenerated secret for OAuth2 application %s of organization %s. +action.organization.oauth2application.remove = Removed OAuth2 application %s of organization %s. +action.organization.secret.add = Added secret %s for organization %s. +action.organization.secret.update = Updated secret %s of organization %s. +action.organization.secret.remove = Removed secret %s of organization %s. +action.organization.webhook.add = Added webhook %s for organization %s. +action.organization.webhook.update = Updated webhook %s of organization %s. +action.organization.webhook.remove = Removed webhook %s of organization %s. + +action.repository.create = Created repository %s. +action.repository.create.fork = Created fork %s of repository %s. +action.repository.archive = Archived repository %s. +action.repository.unarchive = Unarchived repository %s. +action.repository.delete = Deleted repository %s. +action.repository.name = Changed repository name from %s to %s. +action.repository.visibility = Changed visibility of repository %s to %s. +action.repository.convert.fork = Converted repository %s from fork to regular repository. +action.repository.convert.mirror = Converted repository %s from pull mirror to regular repository. +action.repository.mirror.push.add = Added push mirror to %s for repository %s. +action.repository.mirror.push.remove = Removed push mirror to %s for repository %s. +action.repository.signingverification = Changed signing verification of repository %s to %s. +action.repository.transfer.start = Started repository transfer of %s to %s. +action.repository.transfer.finish = Transferred repository %s from %s to %s. +action.repository.transfer.cancel = Canceled transfer of repository %s. +action.repository.wiki.delete = Deleted wiki of repository %s. +action.repository.collaborator.add = Added user %s as collaborator for repository %s. +action.repository.collaborator.access = Changed access mode of collaborator %s of repository %s to %s. +action.repository.collaborator.remove = Removed collaborator %s from repository %s. +action.repository.collaborator.team.add = Added team %s as collaborator for repository %s. +action.repository.collaborator.team.remove = Removed team %s as collaborator from repository %s. +action.repository.branch.default = Changed default branch of repository %s to %s. +action.repository.branch.protection.add = Added branch protection %s for repository %s. +action.repository.branch.protection.update = Updated branch protection %s for repository %s. +action.repository.branch.protection.remove = Removed branch protection %s from repository %s. +action.repository.tag.protection.add = Added tag protection %s for repository %s. +action.repository.tag.protection.update = Updated tag protection %s for repository %s. +action.repository.tag.protection.remove = Removed tag protection %s for repository %s. +action.repository.webhook.add = Added webhook %s for repository %s. +action.repository.webhook.update = Updated webhook %s of repository %s. +action.repository.webhook.remove = Removed webhook %s of repository %s. +action.repository.deploykey.add = Added deploy key %s for repository %s. +action.repository.deploykey.remove = Removed deploy key %s from repository %s. +action.repository.secret.add = Added secret %s for repository %s. +action.repository.secret.update = Updated secret %s of repository %s. +action.repository.secret.remove = Removed secret %s of repository %s. + +action.system.startup = System started [Gitea %s] +action.system.shutdown = System shutdown +action.system.webhook.add = Added instance-wide webhook %s. +action.system.webhook.update = Updated instance-wide webhook %s. +action.system.webhook.remove = Removed instance-wide webhook %s. +action.system.authenticationsource.add = Created authentication source %s of type %s. +action.system.authenticationsource.update = Updated authentication source %s. +action.system.authenticationsource.remove = Removed authentication source %s. +action.system.oauth2application.add = Created instance-wide OAuth2 application %s. +action.system.oauth2application.update = Updated instance-wide OAuth2 application %s. +action.system.oauth2application.secret = Regenerated secret for instance-wide OAuth2 application %s. +action.system.oauth2application.remove = Removed instance-wide OAuth2 application %s. diff --git a/services/audit/audit_test.go b/services/audit/audit_test.go index efd20495ae1a1..9d45b1489d6ad 100644 --- a/services/audit/audit_test.go +++ b/services/audit/audit_test.go @@ -41,11 +41,14 @@ func TestBuildEvent(t *testing.T) { equal( &Event{ - Action: audit_model.UserCreate, - Actor: TypeDescriptor{Type: "user", ID: 2, Object: doer}, - Scope: TypeDescriptor{Type: "user", ID: 1, Object: u}, - Target: TypeDescriptor{Type: "user", ID: 1, Object: u}, - Message: "Created user TestUser.", + Action: audit_model.UserCreate, + Actor: TypeDescriptor{Type: "user", ID: 2, Object: doer}, + Scope: TypeDescriptor{Type: "user", ID: 1, Object: u}, + Target: TypeDescriptor{Type: "user", ID: 1, Object: u}, + MessageContext: MessageContext{ + audit_model.UserCreate, + []any{u.Name}, + }, }, buildEvent( ctx, @@ -59,11 +62,14 @@ func TestBuildEvent(t *testing.T) { ) equal( &Event{ - Action: audit_model.RepositoryMirrorPushAdd, - Actor: TypeDescriptor{Type: "user", ID: 2, Object: doer}, - Scope: TypeDescriptor{Type: "repository", ID: 3, Object: r}, - Target: TypeDescriptor{Type: "push_mirror", ID: 4, Object: m}, - Message: "Added push mirror for repository TestUser/TestRepo.", + Action: audit_model.RepositoryMirrorPushAdd, + Actor: TypeDescriptor{Type: "user", ID: 2, Object: doer}, + Scope: TypeDescriptor{Type: "repository", ID: 3, Object: r}, + Target: TypeDescriptor{Type: "push_mirror", ID: 4, Object: m}, + MessageContext: MessageContext{ + audit_model.RepositoryMirrorPushAdd, + []any{r.FullName()}, + }, }, buildEvent( ctx, diff --git a/services/audit/database.go b/services/audit/database.go index e8a4cbd27968e..7dea05f1495f7 100644 --- a/services/audit/database.go +++ b/services/audit/database.go @@ -12,15 +12,15 @@ import ( func writeToDatabase(ctx context.Context, e *Event) error { _, err := audit_model.InsertEvent(ctx, &audit_model.Event{ - Action: e.Action, - ActorID: e.Actor.ID, - ScopeType: e.Scope.Type, - ScopeID: e.Scope.ID, - TargetType: e.Target.Type, - TargetID: e.Target.ID, - Message: e.Message, - IPAddress: e.IPAddress, - TimestampUnix: timeutil.TimeStamp(e.Time.Unix()), + Action: e.Action, + ActorID: e.Actor.ID, + ScopeType: e.Scope.Type, + ScopeID: e.Scope.ID, + TargetType: e.Target.Type, + TargetID: e.Target.ID, + MessageContext: e.MessageContext.Values, + IPAddress: e.IPAddress, + TimestampUnix: timeutil.TimeStamp(e.Time.Unix()), }) return err } diff --git a/services/audit/display.go b/services/audit/display.go index 03cafbd7c5a26..f7000303fec07 100644 --- a/services/audit/display.go +++ b/services/audit/display.go @@ -53,11 +53,14 @@ func fromDatabaseEvents(ctx context.Context, evs []*audit_model.Event) []*Event func fromDatabaseEvent(ctx context.Context, e *audit_model.Event, c cache) *Event { return &Event{ - Action: e.Action, - Actor: resolveType(ctx, audit_model.TypeUser, e.ActorID, c), - Scope: resolveType(ctx, e.ScopeType, e.ScopeID, c), - Target: resolveType(ctx, e.TargetType, e.TargetID, c), - Message: e.Message, + Action: e.Action, + Actor: resolveType(ctx, audit_model.TypeUser, e.ActorID, c), + Scope: resolveType(ctx, e.ScopeType, e.ScopeID, c), + Target: resolveType(ctx, e.TargetType, e.TargetID, c), + MessageContext: MessageContext{ + e.Action, + e.MessageContext, + }, Time: e.TimestampUnix.AsTime(), IPAddress: e.IPAddress, } diff --git a/services/audit/file.go b/services/audit/file.go index 70a236921943b..2836de72a5620 100644 --- a/services/audit/file.go +++ b/services/audit/file.go @@ -9,16 +9,22 @@ import ( audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util/rotatingfilewriter" ) -var rfw *rotatingfilewriter.RotatingFileWriter +var ( + rfw *rotatingfilewriter.RotatingFileWriter + locale translation.Locale +) func initAuditFile() error { if setting.Audit.FileOptions == nil { return nil } + initAuditFileLocale() + opts := setting.Audit.FileOptions var err error @@ -33,6 +39,10 @@ func initAuditFile() error { return err } +func initAuditFileLocale() { + locale = translation.NewLocale("en-us") +} + func writeToFile(e *Event) error { if rfw == nil { return nil @@ -40,7 +50,7 @@ func writeToFile(e *Event) error { return WriteEventAsJSON(rfw, e) } -func (d TypeDescriptor) MarshalJSON() ([]byte, error) { +func (d *TypeDescriptor) MarshalJSON() ([]byte, error) { type out struct { Type audit_model.ObjectType `json:"type"` ID int64 `json:"id"` @@ -54,6 +64,10 @@ func (d TypeDescriptor) MarshalJSON() ([]byte, error) { }) } +func (mc MessageContext) MarshalJSON() ([]byte, error) { + return json.Marshal(locale.TrString(mc.Action.LocaleKey(), mc.Values...)) +} + func WriteEventAsJSON(w io.Writer, e *Event) error { return json.NewEncoder(w).Encode(e) } diff --git a/services/audit/file_test.go b/services/audit/file_test.go index acad109a74067..69cdbd1952128 100644 --- a/services/audit/file_test.go +++ b/services/audit/file_test.go @@ -13,14 +13,23 @@ import ( audit_model "code.gitea.io/gitea/models/audit" repository_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/web/middleware" "github.com/stretchr/testify/assert" ) func TestWriteEventAsJSON(t *testing.T) { + setting.StaticRootPath = "../../" + setting.Names = []string{"english"} + setting.Langs = []string{"en-US"} + translation.InitLocales(context.Background()) + + initAuditFileLocale() + r := &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"} - m := &repository_model.PushMirror{ID: 4} + m := &repository_model.PushMirror{ID: 4, RemoteAddress: "https://gitea.com/"} doer := &user_model.User{ID: 2, Name: "Doer"} ctx := middleware.WithContextRequest(context.Background(), &http.Request{RemoteAddr: "127.0.0.1:1234"}) @@ -32,6 +41,7 @@ func TestWriteEventAsJSON(t *testing.T) { r, m, "Added push mirror for repository %s.", + m.RemoteAddress, r.FullName(), ) e.Time = time.Time{} @@ -40,7 +50,7 @@ func TestWriteEventAsJSON(t *testing.T) { assert.NoError(t, WriteEventAsJSON(&sb, e)) assert.Equal( t, - `{"action":"repository:mirror:push:add","actor":{"type":"user","id":2,"display_name":"Doer"},"scope":{"type":"repository","id":3,"display_name":"TestUser/TestRepo"},"target":{"type":"push_mirror","id":4,"display_name":""},"message":"Added push mirror for repository TestUser/TestRepo.","time":"0001-01-01T00:00:00Z","ip_address":"127.0.0.1"}`+"\n", + `{"action":"repository:mirror:push:add","actor":{"type":"user","id":2,"display_name":"Doer"},"scope":{"type":"repository","id":3,"display_name":"TestUser/TestRepo"},"target":{"type":"push_mirror","id":4,"display_name":"https://gitea.com/"},"message":"Added push mirror to https://gitea.com/ for repository TestUser/TestRepo.","time":"0001-01-01T00:00:00Z","ip_address":"127.0.0.1"}`+"\n", sb.String(), ) } diff --git a/services/audit/record.go b/services/audit/record.go index b6e82ea65c01c..0a019f5a87ba7 100644 --- a/services/audit/record.go +++ b/services/audit/record.go @@ -5,7 +5,6 @@ package audit import ( "context" - "fmt" "time" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -22,34 +21,39 @@ import ( "code.gitea.io/gitea/modules/setting" ) +type MessageContext struct { + Action audit_model.Action + Values []any +} + type Event struct { - Action audit_model.Action `json:"action"` - Actor TypeDescriptor `json:"actor"` - Scope TypeDescriptor `json:"scope"` - Target TypeDescriptor `json:"target"` - Message string `json:"message"` - Time time.Time `json:"time"` - IPAddress string `json:"ip_address"` + Action audit_model.Action `json:"action"` + Actor TypeDescriptor `json:"actor"` + Scope TypeDescriptor `json:"scope"` + Target TypeDescriptor `json:"target"` + MessageContext MessageContext `json:"message"` + Time time.Time `json:"time"` + IPAddress string `json:"ip_address"` } -func buildEvent(ctx context.Context, action audit_model.Action, actor *user_model.User, scope, target any, message string, v ...any) *Event { +func buildEvent(ctx context.Context, action audit_model.Action, actor *user_model.User, scope, target any, values []any) *Event { return &Event{ - Action: action, - Actor: typeToDescription(actor), - Scope: scopeToDescription(scope), - Target: typeToDescription(target), - Message: fmt.Sprintf(message, v...), - Time: time.Now(), - IPAddress: tryGetIPAddress(ctx), + Action: action, + Actor: typeToDescription(actor), + Scope: scopeToDescription(scope), + Target: typeToDescription(target), + MessageContext: MessageContext{action, values}, + Time: time.Now(), + IPAddress: tryGetIPAddress(ctx), } } -func record(ctx context.Context, action audit_model.Action, actor *user_model.User, scope, target any, message string, v ...any) { +func record(ctx context.Context, action audit_model.Action, actor *user_model.User, scope, target any, values []any) { if !setting.Audit.Enabled { return } - e := buildEvent(ctx, action, actor, scope, target, message, v...) + e := buildEvent(ctx, action, actor, scope, target, values) if err := writeToFile(e); err != nil { log.Error("Error writing audit event to file: %v", err) @@ -60,31 +64,31 @@ func record(ctx context.Context, action audit_model.Action, actor *user_model.Us } func RecordUserImpersonation(ctx context.Context, impersonator, target *user_model.User) { - record(ctx, audit_model.UserImpersonation, impersonator, impersonator, target, "User %s impersonating user %s.", impersonator.Name, target.Name) + record(ctx, audit_model.UserImpersonation, impersonator, impersonator, target, []any{impersonator.Name, target.Name}) } func RecordUserCreate(ctx context.Context, doer, user *user_model.User) { + action := audit_model.UserCreate if user.IsOrganization() { - record(ctx, audit_model.OrganizationCreate, doer, user, user, "Created organization %s.", user.Name) - } else { - record(ctx, audit_model.UserCreate, doer, user, user, "Created user %s.", user.Name) + action = audit_model.OrganizationCreate } + record(ctx, action, doer, user, user, []any{user.Name}) } func RecordUserDelete(ctx context.Context, doer, user *user_model.User) { + action := audit_model.UserDelete if user.IsOrganization() { - record(ctx, audit_model.OrganizationDelete, doer, user, user, "Deleted organization %s.", user.Name) - } else { - record(ctx, audit_model.UserDelete, doer, user, user, "Deleted user %s.", user.Name) + action = audit_model.OrganizationDelete } + record(ctx, action, doer, user, user, []any{user.Name}) } func RecordUserAuthenticationFailTwoFactor(ctx context.Context, user *user_model.User) { - record(ctx, audit_model.UserAuthenticationFailTwoFactor, user, user, user, "Failed two-factor authentication for user %s.", user.Name) + record(ctx, audit_model.UserAuthenticationFailTwoFactor, user, user, user, []any{user.Name}) } func RecordUserAuthenticationSource(ctx context.Context, doer, user *user_model.User) { - record(ctx, audit_model.UserAuthenticationSource, doer, user, user, "Changed authentication source of user %s.", user.Name) + record(ctx, audit_model.UserAuthenticationSource, doer, user, user, []any{user.Name}) } func RecordUserActive(ctx context.Context, doer, user *user_model.User) { @@ -93,7 +97,7 @@ func RecordUserActive(ctx context.Context, doer, user *user_model.User) { status = "inactive" } - record(ctx, audit_model.UserActive, doer, user, user, "Changed activation status of user %s to %s.", user.Name, status) + record(ctx, audit_model.UserActive, doer, user, user, []any{user.Name, status}) } func RecordUserRestricted(ctx context.Context, doer, user *user_model.User) { @@ -102,7 +106,7 @@ func RecordUserRestricted(ctx context.Context, doer, user *user_model.User) { status = "unrestricted" } - record(ctx, audit_model.UserRestricted, doer, user, user, "Changed restricted status of user %s to %s.", user.Name, status) + record(ctx, audit_model.UserRestricted, doer, user, user, []any{user.Name, status}) } func RecordUserAdmin(ctx context.Context, doer, user *user_model.User) { @@ -111,39 +115,39 @@ func RecordUserAdmin(ctx context.Context, doer, user *user_model.User) { status = "normal user" } - record(ctx, audit_model.UserAdmin, doer, user, user, "Changed admin status of user %s to %s.", user.Name, status) + record(ctx, audit_model.UserAdmin, doer, user, user, []any{user.Name, status}) } func RecordUserName(ctx context.Context, doer, user *user_model.User) { + action := audit_model.UserName if user.IsOrganization() { - record(ctx, audit_model.OrganizationName, doer, user, user, "Changed organization name to %s.", user.Name) - } else { - record(ctx, audit_model.UserName, doer, user, user, "Changed user name to %s.", user.Name) + action = audit_model.OrganizationName } + record(ctx, action, doer, user, user, []any{user.Name}) } func RecordUserPassword(ctx context.Context, doer, user *user_model.User) { - record(ctx, audit_model.UserPassword, doer, user, user, "Changed password of user %s.", user.Name) + record(ctx, audit_model.UserPassword, doer, user, user, []any{user.Name}) } func RecordUserPasswordResetRequest(ctx context.Context, doer, user *user_model.User) { - record(ctx, audit_model.UserPasswordResetRequest, doer, user, user, "Requested passwort reset for user %s.", user.Name) + record(ctx, audit_model.UserPasswordResetRequest, doer, user, user, []any{user.Name}) } func RecordUserVisibility(ctx context.Context, doer, user *user_model.User) { + action := audit_model.UserVisibility if user.IsOrganization() { - record(ctx, audit_model.OrganizationVisibility, doer, user, user, "Changed visibility of organization %s to %s.", user.Name, user.Visibility.String()) - } else { - record(ctx, audit_model.UserVisibility, doer, user, user, "Changed visibility of user %s to %s.", user.Name, user.Visibility.String()) + action = audit_model.OrganizationVisibility } + record(ctx, action, doer, user, user, []any{user.Name, user.Visibility.String()}) } func RecordUserEmailPrimaryChange(ctx context.Context, doer, user *user_model.User, email *user_model.EmailAddress) { - record(ctx, audit_model.UserEmailPrimaryChange, doer, user, email, "Changed primary email of user %s to %s.", user.Name, email.Email) + record(ctx, audit_model.UserEmailPrimaryChange, doer, user, email, []any{user.Name, email.Email}) } func RecordUserEmailAdd(ctx context.Context, doer, user *user_model.User, email *user_model.EmailAddress) { - record(ctx, audit_model.UserEmailAdd, doer, user, email, "Added email %s to user %s.", email.Email, user.Name) + record(ctx, audit_model.UserEmailAdd, doer, user, email, []any{email.Email, user.Name}) } func RecordUserEmailActivate(ctx context.Context, doer, user *user_model.User, email *user_model.EmailAddress) { @@ -152,241 +156,241 @@ func RecordUserEmailActivate(ctx context.Context, doer, user *user_model.User, e status = "inactive" } - record(ctx, audit_model.UserEmailActivate, doer, user, email, "Changed activation status of email %s of user %s to %s.", email.Email, user.Name, status) + record(ctx, audit_model.UserEmailActivate, doer, user, email, []any{email.Email, user.Name, status}) } func RecordUserEmailRemove(ctx context.Context, doer, user *user_model.User, email *user_model.EmailAddress) { - record(ctx, audit_model.UserEmailRemove, doer, user, email, "Removed email %s from user %s.", email.Email, user.Name) + record(ctx, audit_model.UserEmailRemove, doer, user, email, []any{email.Email, user.Name}) } func RecordUserTwoFactorEnable(ctx context.Context, doer, user *user_model.User) { - record(ctx, audit_model.UserTwoFactorEnable, doer, user, user, "Enabled two-factor authentication for user %s.", user.Name) + record(ctx, audit_model.UserTwoFactorEnable, doer, user, user, []any{user.Name}) } func RecordUserTwoFactorRegenerate(ctx context.Context, doer, user *user_model.User, tf *auth_model.TwoFactor) { - record(ctx, audit_model.UserTwoFactorRegenerate, doer, user, tf, "Regenerated two-factor authentication secret for user %s.", user.Name) + record(ctx, audit_model.UserTwoFactorRegenerate, doer, user, tf, []any{user.Name}) } func RecordUserTwoFactorDisable(ctx context.Context, doer, user *user_model.User, tf *auth_model.TwoFactor) { - record(ctx, audit_model.UserTwoFactorDisable, doer, user, tf, "Disabled two-factor authentication for user %s.", user.Name) + record(ctx, audit_model.UserTwoFactorDisable, doer, user, tf, []any{user.Name}) } func RecordUserWebAuthAdd(ctx context.Context, doer, user *user_model.User, authn *auth_model.WebAuthnCredential) { - record(ctx, audit_model.UserWebAuthAdd, doer, user, authn, "Added WebAuthn key %s for user %s.", authn.Name, user.Name) + record(ctx, audit_model.UserWebAuthAdd, doer, user, authn, []any{authn.Name, user.Name}) } func RecordUserWebAuthRemove(ctx context.Context, doer, user *user_model.User, authn *auth_model.WebAuthnCredential) { - record(ctx, audit_model.UserWebAuthRemove, doer, user, authn, "Removed WebAuthn key %s from user %s.", authn.Name, user.Name) + record(ctx, audit_model.UserWebAuthRemove, doer, user, authn, []any{authn.Name, user.Name}) } func RecordUserExternalLoginAdd(ctx context.Context, doer, user *user_model.User, externalLogin *user_model.ExternalLoginUser) { - record(ctx, audit_model.UserExternalLoginAdd, doer, user, "Added external login %s for user %s using provider %s.", externalLogin.ExternalID, user.Name, externalLogin.Provider) + record(ctx, audit_model.UserExternalLoginAdd, doer, user, externalLogin.ExternalID, []any{user.Name, externalLogin.Provider}) } func RecordUserExternalLoginRemove(ctx context.Context, doer, user *user_model.User, externalLogin *user_model.ExternalLoginUser) { - record(ctx, audit_model.UserExternalLoginRemove, doer, user, "Removed external login %s for user %s from provider.", externalLogin.ExternalID, user.Name, externalLogin.Provider) + record(ctx, audit_model.UserExternalLoginRemove, doer, user, externalLogin.ExternalID, []any{user.Name, externalLogin.Provider}) } func RecordUserOpenIDAdd(ctx context.Context, doer, user *user_model.User, oid *user_model.UserOpenID) { - record(ctx, audit_model.UserOpenIDAdd, doer, user, oid, "Associated OpenID %s to user %s.", oid.URI, user.Name) + record(ctx, audit_model.UserOpenIDAdd, doer, user, oid, []any{oid.URI, user.Name}) } func RecordUserOpenIDRemove(ctx context.Context, doer, user *user_model.User, oid *user_model.UserOpenID) { - record(ctx, audit_model.UserOpenIDRemove, doer, user, oid, "Removed OpenID %s from user %s.", oid.URI, user.Name) + record(ctx, audit_model.UserOpenIDRemove, doer, user, oid, []any{oid.URI, user.Name}) } func RecordUserAccessTokenAdd(ctx context.Context, doer, user *user_model.User, token *auth_model.AccessToken) { - record(ctx, audit_model.UserAccessTokenAdd, doer, user, token, "Added access token %s for user %s with scope %s.", token.Name, user.Name, token.Scope) + record(ctx, audit_model.UserAccessTokenAdd, doer, user, token, []any{token.Name, user.Name, token.Scope}) } func RecordUserAccessTokenRemove(ctx context.Context, doer, user *user_model.User, token *auth_model.AccessToken) { - record(ctx, audit_model.UserAccessTokenRemove, doer, user, token, "Removed access token %s from user %s.", token.Name, user.Name) + record(ctx, audit_model.UserAccessTokenRemove, doer, user, token, []any{token.Name, user.Name}) } func RecordOAuth2ApplicationAdd(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { if user == nil { - record(ctx, audit_model.SystemOAuth2ApplicationAdd, doer, &systemObject, app, "Created instance-wide OAuth2 application %s", app.Name) + record(ctx, audit_model.SystemOAuth2ApplicationAdd, doer, &systemObject, app, []any{app.Name}) } else if user.IsOrganization() { - record(ctx, audit_model.OrganizationOAuth2ApplicationAdd, doer, user, app, "Created OAuth2 application %s for organization %s", app.Name, user.Name) + record(ctx, audit_model.OrganizationOAuth2ApplicationAdd, doer, user, app, []any{app.Name, user.Name}) } else { - record(ctx, audit_model.UserOAuth2ApplicationAdd, doer, user, app, "Created OAuth2 application %s for user %s", app.Name, user.Name) + record(ctx, audit_model.UserOAuth2ApplicationAdd, doer, user, app, []any{app.Name, user.Name}) } } func RecordOAuth2ApplicationUpdate(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { if user == nil { - record(ctx, audit_model.SystemOAuth2ApplicationUpdate, doer, &systemObject, app, "Updated instance-wide OAuth2 application %s", app.Name) + record(ctx, audit_model.SystemOAuth2ApplicationUpdate, doer, &systemObject, app, []any{app.Name}) } else if user.IsOrganization() { - record(ctx, audit_model.OrganizationOAuth2ApplicationUpdate, doer, user, app, "Updated OAuth2 application %s of organization %s", app.Name, user.Name) + record(ctx, audit_model.OrganizationOAuth2ApplicationUpdate, doer, user, app, []any{app.Name, user.Name}) } else { - record(ctx, audit_model.UserOAuth2ApplicationUpdate, doer, user, app, "Updated OAuth2 application %s of user %s", app.Name, user.Name) + record(ctx, audit_model.UserOAuth2ApplicationUpdate, doer, user, app, []any{app.Name, user.Name}) } } func RecordOAuth2ApplicationSecret(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { if user == nil { - record(ctx, audit_model.SystemOAuth2ApplicationSecret, doer, &systemObject, app, "Regenerated secret for instance-wide OAuth2 application %s", app.Name) + record(ctx, audit_model.SystemOAuth2ApplicationSecret, doer, &systemObject, app, []any{app.Name}) } else if user.IsOrganization() { - record(ctx, audit_model.OrganizationOAuth2ApplicationSecret, doer, user, app, "Regenerated secret for OAuth2 application %s of organization %s", app.Name, user.Name) + record(ctx, audit_model.OrganizationOAuth2ApplicationSecret, doer, user, app, []any{app.Name, user.Name}) } else { - record(ctx, audit_model.UserOAuth2ApplicationSecret, doer, user, app, "Regenerated secret for OAuth2 application %s of user %s", app.Name, user.Name) + record(ctx, audit_model.UserOAuth2ApplicationSecret, doer, user, app, []any{app.Name, user.Name}) } } func RecordUserOAuth2ApplicationGrant(ctx context.Context, doer, owner *user_model.User, app *auth_model.OAuth2Application, grant *auth_model.OAuth2Grant) { - record(ctx, audit_model.UserOAuth2ApplicationGrant, doer, owner, grant, "Granted OAuth2 access to application %s of user %s.", app.Name, owner.Name) + record(ctx, audit_model.UserOAuth2ApplicationGrant, doer, owner, grant, []any{app.Name, owner.Name}) } func RecordUserOAuth2ApplicationRevoke(ctx context.Context, doer, owner *user_model.User, app *auth_model.OAuth2Application, grant *auth_model.OAuth2Grant) { - record(ctx, audit_model.UserOAuth2ApplicationRevoke, doer, owner, grant, "Revoked OAuth2 grant for application %s of user %s.", app.Name, owner.Name) + record(ctx, audit_model.UserOAuth2ApplicationRevoke, doer, owner, grant, []any{app.Name, owner.Name}) } func RecordOAuth2ApplicationRemove(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { if user == nil { - record(ctx, audit_model.SystemOAuth2ApplicationRemove, doer, &systemObject, app, "Removed instance-wide OAuth2 application %s", app.Name) + record(ctx, audit_model.SystemOAuth2ApplicationRemove, doer, &systemObject, app, []any{app.Name}) } else if user.IsOrganization() { - record(ctx, audit_model.OrganizationOAuth2ApplicationRemove, doer, user, app, "Removed OAuth2 application %s of organization %s", app.Name, user.Name) + record(ctx, audit_model.OrganizationOAuth2ApplicationRemove, doer, user, app, []any{app.Name, user.Name}) } else { - record(ctx, audit_model.UserOAuth2ApplicationRemove, doer, user, app, "Removed OAuth2 application %s of user %s", app.Name, user.Name) + record(ctx, audit_model.UserOAuth2ApplicationRemove, doer, user, app, []any{app.Name, user.Name}) } } func RecordUserKeySSHAdd(ctx context.Context, doer, user *user_model.User, key *asymkey_model.PublicKey) { - record(ctx, audit_model.UserKeySSHAdd, doer, user, key, "Added SSH key %s for user %s.", key.Fingerprint, user.Name) + record(ctx, audit_model.UserKeySSHAdd, doer, user, key, []any{key.Fingerprint, user.Name}) } func RecordUserKeySSHRemove(ctx context.Context, doer, user *user_model.User, key *asymkey_model.PublicKey) { - record(ctx, audit_model.UserKeySSHRemove, doer, user, key, "Removed SSH key %s of user %s.", key.Fingerprint, user.Name) + record(ctx, audit_model.UserKeySSHRemove, doer, user, key, []any{key.Fingerprint, user.Name}) } func RecordUserKeyPrincipalAdd(ctx context.Context, doer, user *user_model.User, key *asymkey_model.PublicKey) { - record(ctx, audit_model.UserKeyPrincipalAdd, doer, user, key, "Added principal key %s for user %s.", key.Name, user.Name) + record(ctx, audit_model.UserKeyPrincipalAdd, doer, user, key, []any{key.Name, user.Name}) } func RecordUserKeyPrincipalRemove(ctx context.Context, doer, user *user_model.User, key *asymkey_model.PublicKey) { - record(ctx, audit_model.UserKeyPrincipalRemove, doer, user, key, "Removed principal key %s of user %s.", key.Name, user.Name) + record(ctx, audit_model.UserKeyPrincipalRemove, doer, user, key, []any{key.Name, user.Name}) } func RecordUserKeyGPGAdd(ctx context.Context, doer, user *user_model.User, key *asymkey_model.GPGKey) { - record(ctx, audit_model.UserKeyGPGAdd, doer, user, key, "Added GPG key %s for user %s.", key.KeyID, user.Name) + record(ctx, audit_model.UserKeyGPGAdd, doer, user, key, []any{key.KeyID, user.Name}) } func RecordUserKeyGPGRemove(ctx context.Context, doer, user *user_model.User, key *asymkey_model.GPGKey) { - record(ctx, audit_model.UserKeyGPGRemove, doer, user, key, "Removed GPG key %s of user %s.", key.KeyID, user.Name) + record(ctx, audit_model.UserKeyGPGRemove, doer, user, key, []any{key.KeyID, user.Name}) } func RecordSecretAdd(ctx context.Context, doer, owner *user_model.User, repo *repository_model.Repository, secret *secret_model.Secret) { if owner == nil { - record(ctx, audit_model.RepositorySecretAdd, doer, repo, secret, "Added secret %s for repository %s.", secret.Name, repo.FullName()) + record(ctx, audit_model.RepositorySecretAdd, doer, repo, secret, []any{secret.Name, repo.FullName()}) } else if owner.IsOrganization() { - record(ctx, audit_model.OrganizationSecretAdd, doer, owner, secret, "Added secret %s for organization %s.", secret.Name, owner.Name) + record(ctx, audit_model.OrganizationSecretAdd, doer, owner, secret, []any{secret.Name, owner.Name}) } else { - record(ctx, audit_model.UserSecretAdd, doer, owner, secret, "Added secret %s for user %s.", secret.Name, owner.Name) + record(ctx, audit_model.UserSecretAdd, doer, owner, secret, []any{secret.Name, owner.Name}) } } func RecordSecretUpdate(ctx context.Context, doer, owner *user_model.User, repo *repository_model.Repository, secret *secret_model.Secret) { if owner == nil { - record(ctx, audit_model.RepositorySecretUpdate, doer, repo, secret, "Updated secret %s of repository %s.", secret.Name, repo.FullName()) + record(ctx, audit_model.RepositorySecretUpdate, doer, repo, secret, []any{secret.Name, repo.FullName()}) } else if owner.IsOrganization() { - record(ctx, audit_model.OrganizationSecretUpdate, doer, owner, secret, "Updated secret %s of organization %s.", secret.Name, owner.Name) + record(ctx, audit_model.OrganizationSecretUpdate, doer, owner, secret, []any{secret.Name, owner.Name}) } else { - record(ctx, audit_model.UserSecretUpdate, doer, owner, secret, "Updated secret %s of user %s.", secret.Name, owner.Name) + record(ctx, audit_model.UserSecretUpdate, doer, owner, secret, []any{secret.Name, owner.Name}) } } func RecordSecretRemove(ctx context.Context, doer, owner *user_model.User, repo *repository_model.Repository, secret *secret_model.Secret) { if owner == nil { - record(ctx, audit_model.RepositorySecretRemove, doer, repo, secret, "Removed secret %s of repository %s.", secret.Name, repo.FullName()) + record(ctx, audit_model.RepositorySecretRemove, doer, repo, secret, []any{secret.Name, repo.FullName()}) } else if owner.IsOrganization() { - record(ctx, audit_model.OrganizationSecretRemove, doer, owner, secret, "Removed secret %s of organization %s.", secret.Name, owner.Name) + record(ctx, audit_model.OrganizationSecretRemove, doer, owner, secret, []any{secret.Name, owner.Name}) } else { - record(ctx, audit_model.UserSecretRemove, doer, owner, secret, "Removed secret %s of user %s.", secret.Name, owner.Name) + record(ctx, audit_model.UserSecretRemove, doer, owner, secret, []any{secret.Name, owner.Name}) } } func RecordWebhookAdd(ctx context.Context, doer, owner *user_model.User, repo *repository_model.Repository, hook *webhook_model.Webhook) { if owner == nil && repo == nil { - record(ctx, audit_model.SystemWebhookAdd, doer, &systemObject, hook, "Added instance-wide webhook %s.", hook.URL) + record(ctx, audit_model.SystemWebhookAdd, doer, &systemObject, hook, []any{hook.URL}) } else if repo != nil { - record(ctx, audit_model.RepositoryWebhookAdd, doer, repo, hook, "Added webhook %s for repository %s.", hook.URL, repo.FullName()) + record(ctx, audit_model.RepositoryWebhookAdd, doer, repo, hook, []any{hook.URL, repo.FullName()}) } else if owner.IsOrganization() { - record(ctx, audit_model.OrganizationWebhookAdd, doer, owner, hook, "Added webhook %s for organization %s.", hook.URL, owner.Name) + record(ctx, audit_model.OrganizationWebhookAdd, doer, owner, hook, []any{hook.URL, owner.Name}) } else { - record(ctx, audit_model.UserWebhookAdd, doer, owner, hook, "Added webhook %s for user %s.", hook.URL, owner.Name) + record(ctx, audit_model.UserWebhookAdd, doer, owner, hook, []any{hook.URL, owner.Name}) } } func RecordWebhookUpdate(ctx context.Context, doer, owner *user_model.User, repo *repository_model.Repository, hook *webhook_model.Webhook) { if owner == nil && repo == nil { - record(ctx, audit_model.SystemWebhookUpdate, doer, &systemObject, hook, "Updated instance-wide webhook %s.", hook.URL) + record(ctx, audit_model.SystemWebhookUpdate, doer, &systemObject, hook, []any{hook.URL}) } else if repo != nil { - record(ctx, audit_model.RepositoryWebhookUpdate, doer, repo, hook, "Updated webhook %s of repository %s.", hook.URL, repo.FullName()) + record(ctx, audit_model.RepositoryWebhookUpdate, doer, repo, hook, []any{hook.URL, repo.FullName()}) } else if owner.IsOrganization() { - record(ctx, audit_model.OrganizationWebhookUpdate, doer, owner, hook, "Updated webhook %s of organization %s.", hook.URL, owner.Name) + record(ctx, audit_model.OrganizationWebhookUpdate, doer, owner, hook, []any{hook.URL, owner.Name}) } else { - record(ctx, audit_model.UserWebhookUpdate, doer, owner, hook, "Updated webhook %s of user %s.", hook.URL, owner.Name) + record(ctx, audit_model.UserWebhookUpdate, doer, owner, hook, []any{hook.URL, owner.Name}) } } func RecordWebhookRemove(ctx context.Context, doer, owner *user_model.User, repo *repository_model.Repository, hook *webhook_model.Webhook) { if owner == nil && repo == nil { - record(ctx, audit_model.SystemWebhookRemove, doer, &systemObject, hook, "Removed instance-wide webhook %s.", hook.URL) + record(ctx, audit_model.SystemWebhookRemove, doer, &systemObject, hook, []any{hook.URL}) } else if repo != nil { - record(ctx, audit_model.RepositoryWebhookRemove, doer, repo, hook, "Removed webhook %s of repository %s.", hook.URL, repo.FullName()) + record(ctx, audit_model.RepositoryWebhookRemove, doer, repo, hook, []any{hook.URL, repo.FullName()}) } else if owner.IsOrganization() { - record(ctx, audit_model.OrganizationWebhookRemove, doer, owner, hook, "Removed webhook %s of organization %s.", hook.URL, owner.Name) + record(ctx, audit_model.OrganizationWebhookRemove, doer, owner, hook, []any{hook.URL, owner.Name}) } else { - record(ctx, audit_model.UserWebhookRemove, doer, owner, hook, "Removed webhook %s of user %s.", hook.URL, owner.Name) + record(ctx, audit_model.UserWebhookRemove, doer, owner, hook, []any{hook.URL, owner.Name}) } } func RecordOrganizationTeamAdd(ctx context.Context, doer *user_model.User, org *organization_model.Organization, team *organization_model.Team) { - record(ctx, audit_model.OrganizationTeamAdd, doer, org, team, "Added team %s to organization %s.", team.Name, org.Name) + record(ctx, audit_model.OrganizationTeamAdd, doer, org, team, []any{team.Name, org.Name}) } func RecordOrganizationTeamUpdate(ctx context.Context, doer *user_model.User, org *organization_model.Organization, team *organization_model.Team) { - record(ctx, audit_model.OrganizationTeamUpdate, doer, org, team, "Updated settings of team %s/%s.", org.Name, team.Name) + record(ctx, audit_model.OrganizationTeamUpdate, doer, org, team, []any{org.Name, team.Name}) } func RecordOrganizationTeamRemove(ctx context.Context, doer *user_model.User, org *organization_model.Organization, team *organization_model.Team) { - record(ctx, audit_model.OrganizationTeamRemove, doer, org, team, "Removed team %s from organization %s.", team.Name, org.Name) + record(ctx, audit_model.OrganizationTeamRemove, doer, org, team, []any{team.Name, org.Name}) } func RecordOrganizationTeamPermission(ctx context.Context, doer *user_model.User, org *organization_model.Organization, team *organization_model.Team) { - record(ctx, audit_model.OrganizationTeamPermission, doer, org, team, "Changed permission of team %s/%s to %s.", org.Name, team.Name, team.AccessMode.String()) + record(ctx, audit_model.OrganizationTeamPermission, doer, org, team, []any{org.Name, team.Name, team.AccessMode.String()}) } func RecordOrganizationTeamMemberAdd(ctx context.Context, doer *user_model.User, org *organization_model.Organization, team *organization_model.Team, member *user_model.User) { - record(ctx, audit_model.OrganizationTeamMemberAdd, doer, org, team, "Added user %s to team %s/%s.", member.Name, org.Name, team.Name) + record(ctx, audit_model.OrganizationTeamMemberAdd, doer, org, team, []any{member.Name, org.Name, team.Name}) } func RecordOrganizationTeamMemberRemove(ctx context.Context, doer *user_model.User, org *organization_model.Organization, team *organization_model.Team, member *user_model.User) { - record(ctx, audit_model.OrganizationTeamMemberRemove, doer, org, team, "Removed user %s from team %s/%s.", member.Name, org.Name, team.Name) + record(ctx, audit_model.OrganizationTeamMemberRemove, doer, org, team, []any{member.Name, org.Name, team.Name}) } func RecordRepositoryCreate(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryCreate, doer, repo, repo, "Created repository %s.", repo.FullName()) + record(ctx, audit_model.RepositoryCreate, doer, repo, repo, []any{repo.FullName()}) } func RecordRepositoryCreateFork(ctx context.Context, doer *user_model.User, repo, baseRepo *repository_model.Repository) { - record(ctx, audit_model.RepositoryCreateFork, doer, repo, repo, "Created fork %s of repository %s.", repo.FullName(), baseRepo.FullName()) + record(ctx, audit_model.RepositoryCreateFork, doer, repo, repo, []any{repo.FullName(), baseRepo.FullName()}) } func RecordRepositoryArchive(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryArchive, doer, repo, repo, "Archived repository %s.", repo.FullName()) + record(ctx, audit_model.RepositoryArchive, doer, repo, repo, []any{repo.FullName()}) } func RecordRepositoryUnarchive(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryUnarchive, doer, repo, repo, "Unarchived repository %s.", repo.FullName()) + record(ctx, audit_model.RepositoryUnarchive, doer, repo, repo, []any{repo.FullName()}) } func RecordRepositoryDelete(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) + record(ctx, audit_model.RepositoryDelete, doer, repo, repo, []any{repo.FullName()}) } func RecordRepositoryName(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, previousName string) { - record(ctx, audit_model.RepositoryName, doer, repo, repo, "Changed repository name from %s to %s.", previousName, repo.FullName()) + record(ctx, audit_model.RepositoryName, doer, repo, repo, []any{previousName, repo.FullName()}) } func RecordRepositoryVisibility(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { @@ -395,118 +399,118 @@ func RecordRepositoryVisibility(ctx context.Context, doer *user_model.User, repo status = "private" } - record(ctx, audit_model.RepositoryVisibility, doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), status) + record(ctx, audit_model.RepositoryVisibility, doer, repo, repo, []any{repo.FullName(), status}) } func RecordRepositoryConvertFork(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryConvertFork, doer, repo, repo, "Converted repository %s from fork to regular repository.", repo.FullName()) + record(ctx, audit_model.RepositoryConvertFork, doer, repo, repo, []any{repo.FullName()}) } func RecordRepositoryConvertMirror(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryConvertMirror, doer, repo, repo, "Converted repository %s from pull mirror to regular repository.", repo.FullName()) + record(ctx, audit_model.RepositoryConvertMirror, doer, repo, repo, []any{repo.FullName()}) } func RecordRepositoryMirrorPushAdd(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, mirror *repository_model.PushMirror) { - record(ctx, audit_model.RepositoryMirrorPushAdd, doer, repo, mirror, "Added push mirror to %s for repository %s.", mirror.RemoteAddress, repo.FullName()) + record(ctx, audit_model.RepositoryMirrorPushAdd, doer, repo, mirror, []any{mirror.RemoteAddress, repo.FullName()}) } func RecordRepositoryMirrorPushRemove(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, mirror *repository_model.PushMirror) { - record(ctx, audit_model.RepositoryMirrorPushRemove, doer, repo, mirror, "Removed push mirror to %s for repository %s.", mirror.RemoteAddress, repo.FullName()) + record(ctx, audit_model.RepositoryMirrorPushRemove, doer, repo, mirror, []any{mirror.RemoteAddress, repo.FullName()}) } func RecordRepositorySigningVerification(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositorySigningVerification, doer, repo, repo, "Changed signing verification of repository %s to %s.", repo.FullName(), repo.TrustModel.String()) + record(ctx, audit_model.RepositorySigningVerification, doer, repo, repo, []any{repo.FullName(), repo.TrustModel.String()}) } func RecordRepositoryTransferStart(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, newOwner *user_model.User) { - record(ctx, audit_model.RepositoryTransferStart, doer, repo, repo, "Started repository transfer of %s to %s.", repo.FullName(), newOwner.Name) + record(ctx, audit_model.RepositoryTransferStart, doer, repo, repo, []any{repo.FullName(), newOwner.Name}) } func RecordRepositoryTransferFinish(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, oldOwner *user_model.User) { - record(ctx, audit_model.RepositoryTransferFinish, doer, repo, repo, "Transferred repository %s from %s to %s.", repo.FullName(), oldOwner.Name, repo.OwnerName) + record(ctx, audit_model.RepositoryTransferFinish, doer, repo, repo, []any{repo.FullName(), oldOwner.Name, repo.OwnerName}) } func RecordRepositoryTransferCancel(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryTransferCancel, doer, repo, repo, "Canceled transfer of repository %s.", repo.FullName()) + record(ctx, audit_model.RepositoryTransferCancel, doer, repo, repo, []any{repo.FullName()}) } func RecordRepositoryWikiDelete(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryWikiDelete, doer, repo, repo, "Deleted wiki of repository %s.", repo.FullName()) + record(ctx, audit_model.RepositoryWikiDelete, doer, repo, repo, []any{repo.FullName()}) } func RecordRepositoryCollaboratorAdd(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, collaborator *user_model.User) { - record(ctx, audit_model.RepositoryCollaboratorAdd, doer, repo, collaborator, "Added user %s as collaborator for repository %s.", collaborator.Name, repo.FullName()) + record(ctx, audit_model.RepositoryCollaboratorAdd, doer, repo, collaborator, []any{collaborator.Name, repo.FullName()}) } func RecordRepositoryCollaboratorAccess(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, collaborator *user_model.User, accessMode perm_model.AccessMode) { - record(ctx, audit_model.RepositoryCollaboratorAccess, doer, repo, collaborator, "Changed access mode of collaborator %s of repository %s to %s.", collaborator.Name, repo.FullName(), accessMode.String()) + record(ctx, audit_model.RepositoryCollaboratorAccess, doer, repo, collaborator, []any{collaborator.Name, repo.FullName(), accessMode.String()}) } func RecordRepositoryCollaboratorRemove(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, collaborator *user_model.User) { - record(ctx, audit_model.RepositoryCollaboratorRemove, doer, repo, collaborator, "Removed collaborator %s from repository %s.", collaborator.Name, repo.FullName()) + record(ctx, audit_model.RepositoryCollaboratorRemove, doer, repo, collaborator, []any{collaborator.Name, repo.FullName()}) } func RecordRepositoryCollaboratorTeamAdd(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, team *organization_model.Team) { - record(ctx, audit_model.RepositoryCollaboratorTeamAdd, doer, repo, team, "Added team %s as collaborator for repository %s.", team.Name, repo.FullName()) + record(ctx, audit_model.RepositoryCollaboratorTeamAdd, doer, repo, team, []any{team.Name, repo.FullName()}) } func RecordRepositoryCollaboratorTeamRemove(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, team *organization_model.Team) { - record(ctx, audit_model.RepositoryCollaboratorTeamRemove, doer, repo, team, "Removed team %s as collaborator from repository %s.", team.Name, repo.FullName()) + record(ctx, audit_model.RepositoryCollaboratorTeamRemove, doer, repo, team, []any{team.Name, repo.FullName()}) } func RecordRepositoryBranchDefault(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryBranchDefault, doer, repo, repo, "Changed default branch of repository %s to %s.", repo.FullName(), repo.DefaultBranch) + record(ctx, audit_model.RepositoryBranchDefault, doer, repo, repo, []any{repo.FullName(), repo.DefaultBranch}) } func RecordRepositoryBranchProtectionAdd(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, protectBranch *git_model.ProtectedBranch) { - record(ctx, audit_model.RepositoryBranchProtectionAdd, doer, repo, protectBranch, "Added branch protection %s for repository %s.", protectBranch.RuleName, repo.FullName()) + record(ctx, audit_model.RepositoryBranchProtectionAdd, doer, repo, protectBranch, []any{protectBranch.RuleName, repo.FullName()}) } func RecordRepositoryBranchProtectionUpdate(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, protectBranch *git_model.ProtectedBranch) { - record(ctx, audit_model.RepositoryBranchProtectionUpdate, doer, repo, protectBranch, "Updated branch protection %s for repository %s.", protectBranch.RuleName, repo.FullName()) + record(ctx, audit_model.RepositoryBranchProtectionUpdate, doer, repo, protectBranch, []any{protectBranch.RuleName, repo.FullName()}) } func RecordRepositoryBranchProtectionRemove(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, protectBranch *git_model.ProtectedBranch) { - record(ctx, audit_model.RepositoryBranchProtectionRemove, doer, repo, protectBranch, "Removed branch protection %s from repository %s.", protectBranch.RuleName, repo.FullName()) + record(ctx, audit_model.RepositoryBranchProtectionRemove, doer, repo, protectBranch, []any{protectBranch.RuleName, repo.FullName()}) } func RecordRepositoryTagProtectionAdd(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, protectedTag *git_model.ProtectedTag) { - record(ctx, audit_model.RepositoryTagProtectionAdd, doer, repo, protectedTag, "Added tag protection %s for repository %s.", protectedTag.NamePattern, repo.FullName()) + record(ctx, audit_model.RepositoryTagProtectionAdd, doer, repo, protectedTag, []any{protectedTag.NamePattern, repo.FullName()}) } func RecordRepositoryTagProtectionUpdate(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, protectedTag *git_model.ProtectedTag) { - record(ctx, audit_model.RepositoryTagProtectionUpdate, doer, repo, protectedTag, "Updated tag protection %s for repository %s.", protectedTag.NamePattern, repo.FullName()) + record(ctx, audit_model.RepositoryTagProtectionUpdate, doer, repo, protectedTag, []any{protectedTag.NamePattern, repo.FullName()}) } func RecordRepositoryTagProtectionRemove(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, protectedTag *git_model.ProtectedTag) { - record(ctx, audit_model.RepositoryTagProtectionRemove, doer, repo, protectedTag, "Removed tag protection %s for repository %s.", protectedTag.NamePattern, repo.FullName()) + record(ctx, audit_model.RepositoryTagProtectionRemove, doer, repo, protectedTag, []any{protectedTag.NamePattern, repo.FullName()}) } func RecordRepositoryDeployKeyAdd(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, deployKey *asymkey_model.DeployKey) { - record(ctx, audit_model.RepositoryDeployKeyAdd, doer, repo, deployKey, "Added deploy key %s for repository %s.", deployKey.Name, repo.FullName()) + record(ctx, audit_model.RepositoryDeployKeyAdd, doer, repo, deployKey, []any{deployKey.Name, repo.FullName()}) } func RecordRepositoryDeployKeyRemove(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, deployKey *asymkey_model.DeployKey) { - record(ctx, audit_model.RepositoryDeployKeyRemove, doer, repo, deployKey, "Removed deploy key %s from repository %s.", deployKey.Name, repo.FullName()) + record(ctx, audit_model.RepositoryDeployKeyRemove, doer, repo, deployKey, []any{deployKey.Name, repo.FullName()}) } func RecordSystemStartup(ctx context.Context, doer *user_model.User, version string) { // Do not change this message anymore. We guarantee the stability of this message for users wanting to parse the log themselves to be able to trace back events across gitea versions. - record(ctx, audit_model.SystemStartup, doer, &systemObject, &systemObject, "System started [Gitea %s]", version) + record(ctx, audit_model.SystemStartup, doer, &systemObject, &systemObject, []any{version}) } func RecordSystemShutdown(ctx context.Context, doer *user_model.User) { - record(ctx, audit_model.SystemShutdown, doer, &systemObject, &systemObject, "System shutdown") + record(ctx, audit_model.SystemShutdown, doer, &systemObject, &systemObject, []any{}) } func RecordSystemAuthenticationSourceAdd(ctx context.Context, doer *user_model.User, authSource *auth_model.Source) { - record(ctx, audit_model.SystemAuthenticationSourceAdd, doer, &systemObject, authSource, "Created authentication source %s of type %s.", authSource.Name, authSource.Type.String()) + record(ctx, audit_model.SystemAuthenticationSourceAdd, doer, &systemObject, authSource, []any{authSource.Name, authSource.Type.String()}) } func RecordSystemAuthenticationSourceUpdate(ctx context.Context, doer *user_model.User, authSource *auth_model.Source) { - record(ctx, audit_model.SystemAuthenticationSourceUpdate, doer, &systemObject, authSource, "Updated authentication source %s.", authSource.Name) + record(ctx, audit_model.SystemAuthenticationSourceUpdate, doer, &systemObject, authSource, []any{authSource.Name}) } func RecordSystemAuthenticationSourceRemove(ctx context.Context, doer *user_model.User, authSource *auth_model.Source) { - record(ctx, audit_model.SystemAuthenticationSourceRemove, doer, &systemObject, authSource, "Removed authentication source %s.", authSource.Name) + record(ctx, audit_model.SystemAuthenticationSourceRemove, doer, &systemObject, authSource, []any{authSource.Name}) } diff --git a/services/context/base.go b/services/context/base.go index c4aa467ff4253..411ca6b6ca28e 100644 --- a/services/context/base.go +++ b/services/context/base.go @@ -299,6 +299,10 @@ func (b *Base) Tr(msg string, args ...any) template.HTML { return b.Locale.Tr(msg, args...) } +func (b *Base) TrExpand(msg string, args []any) template.HTML { + return b.Locale.TrExpand(msg, args) +} + func (b *Base) TrN(cnt any, key1, keyN string, args ...any) template.HTML { return b.Locale.TrN(cnt, key1, keyN, args...) } diff --git a/templates/shared/audit/list.tmpl b/templates/shared/audit/list.tmpl index 002de61a9cfde..406d36e706d97 100644 --- a/templates/shared/audit/list.tmpl +++ b/templates/shared/audit/list.tmpl @@ -36,7 +36,7 @@ {{ctx.Locale.Tr "admin.monitor.audit.deleted.type" .Scope.Type .Scope.ID}} {{end}} - {{.Message}} + {{ctx.Locale.TrExpand .Action.LocaleKey .MessageContext.Values}} {{if or .Target.Object (eq .Scope.Type "system")}} {{$url := .Target.HTMLURL}}